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..9333a778af 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; @@ -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/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/COMMON/common_lib.h b/radio/COMMON/common_lib.h index 9eddbc21e3..5812e63aa4 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -493,12 +493,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 @@ -520,7 +537,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 @@ -573,6 +610,37 @@ 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 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 @@ -631,6 +699,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/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/README.md b/radio/rfsimulator/README.md index 40688b755a..298c7ecfda 100644 --- a/radio/rfsimulator/README.md +++ b/radio/rfsimulator/README.md @@ -209,3 +209,55 @@ 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. +* `--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 ` or `rfsimu setbeamids `. They correspond to the CLI parameters described +above and work the same way. + +### 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. diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index aca8a6b030..3f2e8d85c1 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 @@ -28,36 +27,17 @@ #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, - uint32_t CirSize, - bool add_noise) +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 + 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 +58,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 +137,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,48 +204,70 @@ 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); } } } +} +/* + 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(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 // 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 uint64_t dd = channelDesc->channel_offset; - const int nbTx=channelDesc->nb_tx; + 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)); - 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++) { - // 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 struct complex *channelModelEnd=channelModel+channelDesc->channel_length; + for (int l = 0; l < (int)channelDesc->channel_length; l++) { + 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 + } // l } if (channelDesc->Doppler_phase_inc != 0.0) { @@ -279,12 +289,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 11a57193ca..802a2a2e34 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -1,37 +1,34 @@ /* -* 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(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); #endif diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.cpp similarity index 51% rename from radio/rfsimulator/simulator.c rename to radio/rfsimulator/simulator.cpp index c9093be912..a29001e0bf 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,10 @@ * 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 #include #include #include @@ -42,34 +46,30 @@ #include #include -#include #include #include #include "common_lib.h" #define CHANNELMOD_DYNAMICLOAD +extern "C" { +#include #include #include "rfsimulator.h" +} +#include +#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))) #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; @@ -78,37 +78,50 @@ 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" \ " chanmod: enable channel modelisation\n" \ " saviq: enable saving written iqs to a file\n" -// clang-format off -/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -/* configuration parameters for the rfsimulator device */ -/* optname helpstr paramflags XXXptr defXXXval type numelt */ -/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED +#define simBool PARAMFLAG_BOOL | 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 },\ - }; + 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", 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 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); 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}, @@ -116,28 +129,56 @@ 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}, + {"setbeamids", "beam_id1,beam_id2,...", (cmdfunc_t)rfsimu_set_beamids, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, {"", "", NULL}, }; // clang-format on static telnetshell_cmddef_t *setmodel_cmddef = &(rfsimu_cmdarray[1]); static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}}; -static uint64_t CirSize = minCirSize; typedef c16_t sample_t; // 2*16 bits complex number +typedef struct beam_switch_command_t { + std::vector beams; + openair0_timestamp timestamp; +} beam_switch_command_t; + +typedef struct { + std::vector beams; + std::queue cmd_queue; + 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; + rfsim_packet_t *packet_ptr; + size_t payload_sz; + size_t remainToTransferBeam; + std::queue received_packets; } 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; @@ -163,8 +204,122 @@ 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 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); + 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 + 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_beams = cmd.beams; + 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_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. + * + * 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->beams = beam_state->cmd_queue.front().beams; + beam_state->cmd_queue.pop(); + } else { + break; + } + } +} + +/** + * @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) @@ -172,18 +327,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 = 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"); @@ -208,7 +358,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); @@ -230,11 +380,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--; } @@ -250,10 +400,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) { @@ -276,13 +423,14 @@ 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); } - char *buf = _buf; + char *buf = static_cast(_buf); ssize_t l; while (count) { @@ -312,9 +460,70 @@ 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 = {.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; +} + +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; + 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); @@ -336,7 +545,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]); @@ -344,14 +557,68 @@ 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); + } + + std::vector initial_beams = beam_map_to_beams(beam_map); + 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 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.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; +} + +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; char *modeltype = NULL; rfsimulator_state_t *t = (rfsimulator_state_t *)arg; @@ -384,7 +651,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, @@ -459,7 +726,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; @@ -520,9 +788,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, }; int s = getaddrinfo(NULL, port, &hints, &results); @@ -619,7 +887,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; @@ -661,9 +929,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, + std::vector tx_beams, int flags) { mutexlock(t->Sockmutex); @@ -673,27 +942,24 @@ 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, 0, 0, beams_to_beam_map(tx_beams)}; fullwrite(b->conn_sock, &header, sizeof(header), t); - if (nbAnt == 1) { - fullwrite(b->conn_sock, samplesVoid[0], sampleToByte(nsamps, nbAnt), t); - } else { - sample_t tmpSamples[nsamps][nbAnt]; + 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[a]; - for (int s = 0; s < nsamps; s++) - tmpSamples[s][a] = in[s]; + sample_t *in = (sample_t *)samplesVoid[indices[beam]][a]; + fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t); } - fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt), t); } } } - if (t->lastWroteTS != 0 && fabs((double)t->lastWroteTS - timestamp) > (double)CirSize) - LOG_W(HW, "Discontinuous TX gap too large Tx:%lu, %lu\n", t->lastWroteTS, timestamp); - if (t->lastWroteTS > timestamp) LOG_W(HW, "Not supported to send Tx out of order %lu, %lu\n", t->lastWroteTS, timestamp); @@ -712,23 +978,54 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, nsamps, timestamp, timestamp + nsamps, - signal_energy(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], sampleToByte(nsamps, 1))); + T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); 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; - return rfsimulator_write_internal(device->priv, timestamp, samplesVoid, nsamps, nbAnt, flags); + int nsamps_initial = nsamps; + rfsimulator_state_t *t = static_cast(device->priv); + void *samples[num_beams][nbAnt]; + void **samples_ptr[num_beams]; + for (int beam = 0; beam < num_beams; beam++) { + samples_ptr[beam] = samples[beam]; + for (int aatx = 0; aatx < nbAnt; aatx++) { + samples[beam][aatx] = samplesVoid[beam][aatx]; + } + } + while (nsamps > 0) { + uint32_t nsamps_beam_map; + 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]; + samples_ptr[beam][aatx] = (void *)(ptr + nsamps_beam_map * sizeof(sample_t)); + } + } + timestamp += nsamps_beam_map; + nsamps -= nsamps_beam_map; + } + clear_beam_queue(&t->beam_ctrl->tx, timestamp + nsamps); + return nsamps_initial; +} + +static int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int cc, int flags) +{ + void **tmp = buff; + return rfsimulator_write_beams(device, timestamp, &tmp, nsamps, cc, 1, flags); } static bool add_client(rfsimulator_state_t *t) @@ -758,7 +1055,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 = {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); @@ -770,69 +1067,121 @@ 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 - + 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 < b->th.timestamp) { - // We have a transmission hole to fill, like TDD - // we create no signal samples up to the beginning of this reception + if (b->lastReceivedTS < (int64_t)b->th.timestamp) { 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 > b->th.timestamp) { + } else if (b->lastReceivedTS > (int64_t)b->th.timestamp) { LOG_W(HW, "Received data in past: current is %lu, new reception: %lu!\n", b->lastReceivedTS, b->th.timestamp); b->trashingPacket = true; } - // verify the time gap is not too large - mutexlock(t->Sockmutex); - if (t->lastWroteTS != 0 && (fabs((double)t->lastWroteTS - b->lastReceivedTS) > (double)CirSize)) - LOG_W(HW, "UEsock(%d) Tx/Rx shift too large Tx:%lu, Rx:%lu\n", b->conn_sock, t->lastWroteTS, b->lastReceivedTS); - mutexunlock(t->Sockmutex); } - // move back the pointer to overwrite the header, we don't need it anymore - b->transferPtr = (char *)&b->circularBuf[(b->lastReceivedTS * b->th.nbAnt) % CirSize]; - // we now need to read the samples - b->remainToTransfer = sampleToByte(b->th.size, b->th.nbAnt); - if (!b->remainToTransfer) - b->headerMode = true; // We got a header with 0 antennas, no I/Q to read + + 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; + 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) +/** + * @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 + * @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 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) { - if (b->transferPtr == b->circularBufEnd) - b->transferPtr = (char *)b->circularBuf; + // Assume received_packets is ordered by timestamp + 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; + } + if (pkt->header.timestamp > start_timestamp + num_samples) { + // This packet is after the end of the buffer, stop processing + break; + } - if (!b->trashingPacket) { - b->lastReceivedTS = b->th.timestamp + b->th.size - byteToSample(b->remainToTransfer, b->th.nbAnt); - LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); - } - - if (b->remainToTransfer == 0) { - LOG_D(HW, "UEsock: %d Completed block reception: %ld\n", b->conn_sock, b->lastReceivedTS); - b->headerMode = true; - b->transferPtr = (char *)&b->th; - b->remainToTransfer = sizeof(samplesBlockHeader_t); - b->trashingPacket = false; + // 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); + 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]; + 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(); } } @@ -850,7 +1199,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); @@ -864,19 +1213,12 @@ 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; } - 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 = recv(b->conn_sock, b->transferPtr, blockSz, MSG_DONTWAIT); + 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); @@ -885,17 +1227,142 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) 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) { + 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; + } + } } 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, + bool is_first_beam) { - rfsimulator_state_t *t = device->priv; + 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]; + + 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 + if (!channel_modelling) { + memset(temp_array, 0, sizeof(temp_array)); + channel_modelling = true; + } + const uint64_t channel_offset = ptr->channel_model->channel_offset; + const uint64_t channel_length = ptr->channel_model->channel_length; + 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 { + 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 - 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 - 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++) { + 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++) { + 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; + 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++) { + 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 (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); + } + } + } +} + +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, "Enter rfsimulator_read, expect %d samples, will release at TS: %ld, nbAnt %d\n", nsamps, @@ -907,7 +1374,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) { @@ -916,8 +1383,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; @@ -934,9 +1402,9 @@ 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) { - have_to_wait = true; - break; + if (b->conn_sock != -1 && (t->nextRxTstamp + nsamps) > b->lastReceivedTS) { + have_to_wait = true; + break; } } @@ -954,92 +1422,42 @@ 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)); - // Clear the output buffer - for (int a = 0; a < nbAnt; a++) - memset(samplesVoid[a], 0, sampleToByte(nsamps, 1)); - cf_t temp_array[nbAnt][nsamps]; - bool apply_noise_per_channel = get_noise_power_dBFS() == INVALID_DBFS_VALUE; - int num_chanmod_channels = 0; - // Add all input nodes signal in the output buffer for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { buffer_t *ptr = &t->buf[sock]; - if (ptr->circularBuf && !ptr->trashingPacket) { - bool reGenerateChannel = false; - - // fixme: when do we regenerate - // it seems legacy behavior is: never in UL, each frame in DL - if (reGenerateChannel) - random_channel(ptr->channel_model, 0); - - 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) { - memset(temp_array, 0, sizeof(temp_array)); - } - num_chanmod_channels++; - rxAddInput(ptr->circularBuf, - temp_array[a_rx], - a_rx, - ptr->channel_model, - nsamps, - t->nextRxTstamp, - CirSize, - apply_noise_per_channel); - } else { // no channel modeling - int nbAnt_tx = ptr->th.nbAnt; // number of Tx antennas - int firstIndex = (CirSize + t->nextRxTstamp - t->chan_offset) % CirSize; - sample_t *out = (sample_t *)samplesVoid[a_rx]; - if (nbAnt_tx == 1 && t->nb_cnx == 1) { // optimized for 1 Tx and 1 UE - sample_t *firstSample = (sample_t *)&(ptr->circularBuf[firstIndex]); - if (firstIndex + nsamps > CirSize) { - int tailSz = CirSize - firstIndex; - memcpy(out, firstSample, sampleToByte(tailSz, 1)); - memcpy(out + tailSz, &ptr->circularBuf[0], sampleToByte(nsamps - tailSz, 1)); - } else { - memcpy(out, firstSample, sampleToByte(nsamps, 1)); - } - } else { - // SIMD (with simde) optimization might be added here later - LOG_D(HW, "nbAnt_tx %d nbAnt %d\n", nbAnt_tx, nbAnt); - double H_awgn_mimo_coeff[nbAnt_tx]; - for (int a_tx = 0; a_tx < nbAnt_tx; a_tx++) { - uint32_t ant_diff = abs(a_tx - a_rx); - H_awgn_mimo_coeff[a_tx] = ant_diff ? (0.2 / ant_diff) : 1.0; - } - for (int i = 0; i < nsamps; i++) { // loop over nsamps - for (int a_tx = 0; a_tx < nbAnt_tx; a_tx++) { // sum up signals from nbAnt_tx antennas - out[i].r += (short)(ptr->circularBuf[((firstIndex + i) * nbAnt_tx + a_tx) % CirSize].r * H_awgn_mimo_coeff[a_tx]); - out[i].i += (short)(ptr->circularBuf[((firstIndex + i) * nbAnt_tx + a_tx) % CirSize].i * H_awgn_mimo_coeff[a_tx]); - } // end for a_tx - } // end for i (number of samps) - } // end of 1 tx antenna optimization - } // end of no channel modeling - } // end for a (number of rx antennas) + if (ptr->conn_sock != -1 && ptr->channel_model != NULL) { + update_channel_model(ptr->channel_model, t->nextRxTstamp); } } - 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); - } + + if (t->poll_telnetcmdq) + t->poll_telnetcmdq(t->telnetcmd_qid, t); + + // Clear the output buffer + for (int beam = 0; beam < num_beams; beam++) + for (int a = 0; a < nbAnt; a++) + memset(samplesVoid[beam][a], 0, sampleToByte(nsamps, 1)); + + openair0_timestamp timestamp = t->nextRxTstamp; + int nsamps_to_process = nsamps; + while (nsamps_to_process > 0) { + uint32_t nsamps_beam_map; + 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_beams.size()); } - } 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 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_beams[beam], beam == 0); } + timestamp += nsamps_beam_map; + nsamps_to_process -= nsamps_beam_map; } struct timespec end_time; @@ -1055,32 +1473,59 @@ 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, *ptimestamp, t->nextRxTstamp, - signal_energy(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], sampleToByte(nsamps, 1))); + 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 - 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); + } + } return nsamps; } -static int rfsimulator_get_stats(openair0_device *device) { +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; } -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) { - rfsimulator_state_t *s = device->priv; +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]; 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); + delete s->beam_ctrl; close(s->epollfd); free(s); } @@ -1092,40 +1537,41 @@ 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) { - rfsimulator_state_t *s = device->priv; +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; } -__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 = 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; rfsimulator->sample_rate = openair0_cfg->sample_rate; rfsimulator->rx_freq = openair0_cfg->rx_freq[0]; rfsimulator->tx_bw = openair0_cfg->tx_bw; + rfsimulator->beam_ctrl = new rfsim_beam_ctrl_t; rfsimulator_readconfig(rfsimulator); if (rfsimulator->prop_delay_ms > 0.0) rfsimulator->chan_offset = ceil(rfsimulator->sample_rate * rfsimulator->prop_delay_ms / 1000); if (rfsimulator->chan_offset != 0) { - if (CirSize < minCirSize + rfsimulator->chan_offset) { - CirSize = minCirSize + rfsimulator->chan_offset; - LOG_I(HW, "CirSize = %lu\n", CirSize); - } rfsimulator->prop_delay_ms = rfsimulator->chan_offset * 1000 / rfsimulator->sample_rate; LOG_I(HW, "propagation delay %f ms, %lu samples\n", rfsimulator->prop_delay_ms, rfsimulator->chan_offset); } @@ -1143,12 +1589,18 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { device->trx_set_gains_func = rfsimulator_set_gains; device->trx_write_func = rfsimulator_write; device->trx_read_func = rfsimulator_read; + 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; device->openair0_cfg = &openair0_cfg[0]; 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; diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 2b40f2f05d..38dea17d76 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -548,6 +548,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; @@ -657,6 +669,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) { randominit(); @@ -673,6 +695,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];