From 7fe34d779b23c28f37451614464c2c450a35a3ad Mon Sep 17 00:00:00 2001 From: Teodora Date: Tue, 9 Dec 2025 12:43:16 +0100 Subject: [PATCH 01/43] Fix the PRACH FFT size for 7.2 interface We assumed that PRACH FFT size is equivalent to PxSCH FFT size. However, the parameter ru_config->fftSize is used for the C-plane Section Type 3 which is dedicated for PRACH and mixed numerology. Based on the PRACH format, short or long, the PRACH values 256 or 1024 shall be filled. Co-authored-by: knopp --- executables/nr-ru.c | 1 + radio/COMMON/common_lib.h | 2 ++ radio/fhi_72/oaioran.c | 4 ++-- radio/fhi_72/oran-config.c | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/executables/nr-ru.c b/executables/nr-ru.c index ad4690c034..279b6f5142 100644 --- a/executables/nr-ru.c +++ b/executables/nr-ru.c @@ -896,6 +896,7 @@ static void fill_split7_2_config(split7_config_t *split7, const nfapi_nr_config_ } } + split7->prach_fftSize = prach_config->prach_sequence_length.value == 0 ? 10 : 8; // need to handle 5kHz cases better than this split7->fftSize = log2(fp->ofdm_symbol_size); // M-plane related parameters diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index b87a6a8abf..9eddbc21e3 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -199,6 +199,8 @@ typedef struct split7_config { /*! this is the exponent in 2^X for the FFT size */ uint16_t fftSize; + /*! this is the ceil of the exponent in 2^X for the prach FFT size */ + uint16_t prach_fftSize; // M-plane related parameters uint16_t dl_k0[5]; uint16_t ul_k0[5]; diff --git a/radio/fhi_72/oaioran.c b/radio/fhi_72/oaioran.c index 31c754738d..b614ae99b5 100644 --- a/radio/fhi_72/oaioran.c +++ b/radio/fhi_72/oaioran.c @@ -338,7 +338,7 @@ int xran_fh_rx_read_slot(ru_info_t *ru, int *frame, int *slot) const struct xran_fh_init *fh_init = get_xran_fh_init(); int nPRBs = fh_cfg->nULRBs; - int fftsize = 1 << fh_cfg->ru_conf.fftSize; + int fftsize = 1 << fh_cfg->nULFftSize; int slot_offset_rxdata = 3 & (*slot); uint32_t slot_size = 4 * 14 * fftsize; @@ -496,7 +496,7 @@ int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp) const struct xran_fh_init *fh_init = get_xran_fh_init(); const struct xran_fh_config *fh_cfg = get_xran_fh_config(0); int nPRBs = fh_cfg->nDLRBs; - int fftsize = 1 << fh_cfg->ru_conf.fftSize; + int fftsize = 1 << fh_cfg->nDLFftSize; int nb_tx_per_ru = ru->nb_tx / fh_init->xran_ports; int nb_rx_per_ru = ru->nb_rx / fh_init->xran_ports; diff --git a/radio/fhi_72/oran-config.c b/radio/fhi_72/oran-config.c index 684d44e7cf..51374e79fe 100644 --- a/radio/fhi_72/oran-config.c +++ b/radio/fhi_72/oran-config.c @@ -921,8 +921,8 @@ static bool set_fh_config(void *mplane_api, int ru_idx, int num_rus, enum xran_c fh_config->neAxc = RTE_MAX(oai0->tx_num_channels / num_rus, oai0->rx_num_channels / num_rus); // number of eAxc supported on one CC = max(PDSCH, PUSCH) fh_config->neAxcUl = 0; // number of eAxc supported on one CC for UL direction = PUSCH; used only if XRAN_CATEGORY_B fh_config->nAntElmTRx = 0; // number of antenna elements for TX and RX = SRS; used only if XRAN_CATEGORY_B - fh_config->nDLFftSize = 0; // DL FFT size; not used in xran - fh_config->nULFftSize = 0; // UL FFT size; not used in xran + fh_config->nDLFftSize = oai0->split7.fftSize; // DL FFT size; not used in xran + fh_config->nULFftSize = oai0->split7.fftSize; // UL FFT size; not used in xran fh_config->nDLRBs = oai0->num_rb_dl; // DL PRB; used in oaioran.c/oran-init.c; not used in xran, neither in E nor in F release fh_config->nULRBs = oai0->num_rb_dl; // UL PRB; used in oaioran.c/oran-init.c; in xran E release not used so the patch fixes it, but in xran F release this value is properly used fh_config->nDLAbsFrePointA = 0; // Abs Freq Point A of the Carrier Center Frequency for in KHz Value; not used in xran @@ -966,7 +966,7 @@ static bool set_fh_config(void *mplane_api, int ru_idx, int num_rus, enum xran_c // fh_config->srs_conf = {0}; if (!set_fh_frame_config(oai0, &fh_config->frame_conf)) return false; - if (!set_fh_ru_config(mplane_api, rup, oai0->split7.fftSize, nru, xran_cat, &fh_config->ru_conf)) + if (!set_fh_ru_config(mplane_api, rup, oai0->split7.prach_fftSize, nru, xran_cat, &fh_config->ru_conf)) return false; fh_config->bbdev_enc = NULL; // call back to poll BBDev encoder From 6bd3545a78034179db88873686e10150b272c3c9 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 8 Oct 2025 22:38:48 +0200 Subject: [PATCH 02/43] RFsimulator C++ conversion Convert rfsimulator C source code to C++ to access to STL. --- common/config/config_cmdline.c | 2 +- common/config/config_common.c | 18 +++--- common/config/config_common.h | 16 +++--- common/config/config_load_configmodule.c | 2 +- common/config/config_load_configmodule.h | 4 +- common/config/config_paramdesc.h | 4 +- common/config/config_userapi.c | 8 +-- common/config/config_userapi.h | 10 ++-- common/config/yaml/tests/test_yaml_config.cpp | 3 +- common/utils/load_module_shlib.c | 2 +- common/utils/load_module_shlib.h | 2 +- common/utils/telnetsrv/telnetsrv.h | 4 +- openair1/SIMULATION/TOOLS/random_channel.c | 10 ++-- openair1/SIMULATION/TOOLS/sim.h | 2 +- radio/rfsimulator/CMakeLists.txt | 2 +- .../{simulator.c => simulator.cpp} | 55 ++++++++++--------- 16 files changed, 73 insertions(+), 71 deletions(-) rename radio/rfsimulator/{simulator.c => simulator.cpp} (95%) diff --git a/common/config/config_cmdline.c b/common/config/config_cmdline.c index c3f3d5ae21..f8b10ad14b 100644 --- a/common/config/config_cmdline.c +++ b/common/config/config_cmdline.c @@ -213,7 +213,7 @@ int config_check_unknown_cmdlineopt(configmodule_interface_t *cfg, char *prefix) return unknowndetected; } /* config_check_unknown_cmdlineopt */ -int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, char *prefix) +int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, const char *prefix) { int c = cfg->argc; int i,j; diff --git a/common/config/config_common.c b/common/config/config_common.c index c617c426d7..0b4b8f76d6 100644 --- a/common/config/config_common.c +++ b/common/config/config_common.c @@ -118,7 +118,7 @@ void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree) return ptr; } -int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->iptr), 1); @@ -132,7 +132,7 @@ int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, return status; } -int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->i64ptr), 1); @@ -150,7 +150,7 @@ int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoption return status; } -int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -167,7 +167,7 @@ int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgopti return status; } -int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -190,7 +190,7 @@ int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptio return status; } -int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -210,7 +210,7 @@ int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgo return status; } -int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->dblptr), 1); @@ -224,7 +224,7 @@ int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptio return status; } -int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *ipv4addr) +int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *ipv4addr) { config_check_valptr(cfg, cfgoptions, sizeof(*cfgoptions->uptr), 1); int rst=inet_pton(AF_INET, ipv4addr,cfgoptions->uptr ); @@ -246,7 +246,7 @@ int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions return 0; } -int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix) +int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix) { int status = 0; @@ -322,7 +322,7 @@ void config_assign_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, ch * @param[in] prefix Optional prefix for the parameter name (can be NULL). * @return 1 if the default value was set, 0 if ignored, or -1 on error/unsupported type. */ -int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, char *prefix) +int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, const char *prefix) { if( (cfgoption->paramflags & PARAMFLAG_DONOTREAD) != 0) { return 0; diff --git a/common/config/config_common.h b/common/config/config_common.h index 00211c9519..0400e703c7 100644 --- a/common/config/config_common.h +++ b/common/config/config_common.h @@ -41,16 +41,16 @@ extern "C" #endif void config_check_valptr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int elt_sz, int nb_elt); /* functions to set a parameter to its default value */ -int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); -int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *prefix); +int config_setdefault_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_int64(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_intlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_string(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_stringlist(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_double(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); +int config_setdefault_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *prefix); void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree); void config_assign_int(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *fullname, int val); -int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, char *prefix); +int config_common_getdefault(configmodule_interface_t *cfg, paramdef_t *cfgoption, const char *prefix); #ifdef __cplusplus } diff --git a/common/config/config_load_configmodule.c b/common/config/config_load_configmodule.c index f5dfe6137f..5d8bb2cfcc 100644 --- a/common/config/config_load_configmodule.c +++ b/common/config/config_load_configmodule.c @@ -150,7 +150,7 @@ int config_cmdlineonly_getlist(configmodule_interface_t *cfg, return 0; } -int config_cmdlineonly_get(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, char *prefix) +int config_cmdlineonly_get(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int numoptions, const char *prefix) { int defval; int fatalerror=0; diff --git a/common/config/config_load_configmodule.h b/common/config/config_load_configmodule.h index d5f87d0f8b..2c45f788d4 100644 --- a/common/config/config_load_configmodule.h +++ b/common/config/config_load_configmodule.h @@ -59,10 +59,10 @@ #define CONFIG_ABORT (1<<21) // config failed,abort execution #define CONFIG_NOOOPT (1<<22) // no -O option found when parsing command line struct configmodule_interface; -typedef int (*configmodule_getfunc_t)(struct configmodule_interface *, paramdef_t *, int numparams, char *prefix); +typedef int (*configmodule_getfunc_t)(struct configmodule_interface *, paramdef_t *, int numparams, const char *prefix); typedef int ( *configmodule_getlistfunc_t)(struct configmodule_interface *, paramlist_def_t *, paramdef_t *, int numparams, char *prefix); -typedef int (*configmodule_setfunc_t)(paramdef_t *cfgoptions, int numoptions, char *prefix); +typedef int (*configmodule_setfunc_t)(paramdef_t *cfgoptions, int numoptions, const char *prefix); typedef void (*configmodule_endfunc_t)(struct configmodule_interface *cfg); typedef int (*configmodule_initfunc_t)(struct configmodule_interface *cfg); diff --git a/common/config/config_paramdesc.h b/common/config/config_paramdesc.h index 8ddd507ee6..904b9dfcb0 100644 --- a/common/config/config_paramdesc.h +++ b/common/config/config_paramdesc.h @@ -105,7 +105,7 @@ typedef union checkedparam { #define DEFAULT_EXTRA_SZ 256 typedef struct paramdef { char optname[MAX_OPTNAME_SIZE]; /* parameter name, can be used as long command line option */ - char *helpstr; /* help string */ + const char *helpstr; /* help string */ unsigned int paramflags; /* value is a "ored" combination of above PARAMFLAG_XXXX values */ union { /* pointer to the parameter value, completed by the config module */ char **strptr; @@ -122,7 +122,7 @@ typedef struct paramdef { void *voidptr; } ; union { /* default parameter value, to be used when PARAMFLAG_MANDATORY is not specified */ - char *defstrval; + const char *defstrval; char **defstrlistval; uint32_t defuintval; int defintval; diff --git a/common/config/config_userapi.c b/common/config/config_userapi.c index 9f7477f5cd..1a831d28c5 100644 --- a/common/config/config_userapi.c +++ b/common/config/config_userapi.c @@ -71,7 +71,7 @@ int config_get_processedint(configmodule_interface_t *cfg, paramdef_t *cfgoption return ret; } -void config_printhelp(paramdef_t *params,int numparams, char *prefix) { +void config_printhelp(paramdef_t *params,int numparams, const char *prefix) { printf("\n-----Help for section %-26s: %03i entries------\n",(prefix==NULL)?"(root section)":prefix,numparams); for (int i=0 ; irtflags |= P; } #define CONFIG_CLEARRTFLAG(P) if (config_get_if()) { config_get_if()->rtflags &= (~P); } #define CONFIG_ISPARAMFLAGSET(P,F) ( !!(P.paramflags & F)) -int config_paramidx_fromname(paramdef_t *params, int numparams, char *name); +int config_paramidx_fromname(paramdef_t *params, int numparams, const char *name); /* utility functions, to be used by configuration module and/or configuration libraries */ -void config_printhelp(paramdef_t *, int numparams, char *prefix); -int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *params, int numparams, char *prefix); -int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, char *ipv4addr); +void config_printhelp(paramdef_t *, int numparams, const char *prefix); +int config_process_cmdline(configmodule_interface_t *cfg, paramdef_t *params, int numparams, const char *prefix); +int config_assign_ipv4addr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, const char *ipv4addr); /* apis to get/check parameters, to be used by oai modules, at configuration time */ #define CONFIG_CHECKALLSECTIONS "ALLSECTIONS" int config_check_unknown_cmdlineopt(configmodule_interface_t *cfg, char *prefix); -int config_get(configmodule_interface_t *cfg, paramdef_t *params, int numparams, char *prefix); +int config_get(configmodule_interface_t *cfg, paramdef_t *params, int numparams, const char *prefix); int config_getlist(configmodule_interface_t *cfg, paramlist_def_t *ParamList, paramdef_t *params, int numparams, char *prefix); /* apis to set some of the paramdef_t fields before using the get/getlist api's */ diff --git a/common/config/yaml/tests/test_yaml_config.cpp b/common/config/yaml/tests/test_yaml_config.cpp index 814e3a3635..b2bd53e349 100644 --- a/common/config/yaml/tests/test_yaml_config.cpp +++ b/common/config/yaml/tests/test_yaml_config.cpp @@ -411,7 +411,7 @@ TEST(yaml_config, test_read_ipv4) { config_yaml_get(cfg, &p, 1, prefix); printf("%x\n", addr); - p.defstrval = strdup("10.0.0.1"); + p.defstrval = "10.0.0.1"; strncpy(p.optname, "ipv4_3", sizeof(p.optname) - 1); config_yaml_get(cfg, &p, 1, prefix); printf("%x\n", addr); @@ -419,7 +419,6 @@ TEST(yaml_config, test_read_ipv4) { config_yaml_end(cfg); free(cfg->cfgP[0]); end_configmodule(cfg); - free(p.defstrval); } TEST(yaml_config, yaml_read_str_as_int) { diff --git a/common/utils/load_module_shlib.c b/common/utils/load_module_shlib.c index 825012ce6a..13eec39776 100644 --- a/common/utils/load_module_shlib.c +++ b/common/utils/load_module_shlib.c @@ -252,7 +252,7 @@ load_module_shlib_exit: return ret; } -void * get_shlibmodule_fptr(char *modname, char *fname) +void * get_shlibmodule_fptr(const char *modname, const char *fname) { for (int i=0; i" static telnetshell_cmddef_t channelmod_cmdarray[] = { @@ -1696,7 +1696,7 @@ channel_desc_t *new_channel_desc_scm(uint8_t nb_tx, return(chan_desc); } /* channel_desc_t *new_channel_desc_scm */ -channel_desc_t *find_channel_desc_fromname( char *modelname ) { +channel_desc_t *find_channel_desc_fromname(const char *modelname) { for(int i=0; imodel_name,modelname) == 0) @@ -2029,7 +2029,7 @@ static int channelmod_print_help(char *buff, int debug, telnet_printfunc_t prnt static char *pnames[] = {"riceanf", "aoa", "randaoa", "ploss", "noise_power_dB", "offset", "forgetf", NULL}; static char *pformat[] = {"%lf", "%lf", "%i", "%lf", "%lf", "%i", "%lf", NULL}; -int get_channel_params(char *buf, int debug, void *vdata, telnet_printfunc_t prnt) +int get_channel_params(const char *buf, int debug, void *vdata, telnet_printfunc_t prnt) { if (buf == NULL) { LOG_I(UTIL, "%s received NULL buffer\n", __FUNCTION__); @@ -2111,7 +2111,7 @@ static void display_channelmodel(channel_desc_t *cd,int debug, telnet_printfunc_ } } -int get_currentchannels_type(char *buf, int debug, void *vdata, telnet_printfunc_t prnt) +int get_currentchannels_type(const char *buf, int debug, void *vdata, telnet_printfunc_t prnt) { webdatadef_t *tdata; if (buf == NULL) { diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 7104d524e2..6e60ce76de 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -347,7 +347,7 @@ channel_desc_t *new_channel_desc_scm(uint8_t nb_tx, double path_loss_dB, float noise_power_dB); -channel_desc_t *find_channel_desc_fromname( char *modelname ); +channel_desc_t *find_channel_desc_fromname(const char *modelname); /** diff --git a/radio/rfsimulator/CMakeLists.txt b/radio/rfsimulator/CMakeLists.txt index ad4d92f108..2b04f7dd89 100644 --- a/radio/rfsimulator/CMakeLists.txt +++ b/radio/rfsimulator/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(rfsimulator MODULE - simulator.c + simulator.cpp apply_channelmod.c ../../openair1/PHY/TOOLS/signal_energy.c ) diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.cpp similarity index 95% rename from radio/rfsimulator/simulator.c rename to radio/rfsimulator/simulator.cpp index c9093be912..bb61a48e89 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.cpp @@ -42,12 +42,14 @@ #include #include -#include #include #include #include "common_lib.h" #define CHANNELMOD_DYNAMICLOAD +extern "C" { +#include #include +} #include "rfsimulator.h" #define PORT 4043 // default TCP port for this simulator @@ -104,7 +106,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; }; // clang-format on static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); -extern int get_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); // in random_channel.c +extern int get_currentchannels_type(const char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); // in random_channel.c static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_getdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); @@ -172,7 +174,7 @@ static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock) uint64_t buff_index = bridge->next_buf++ % MAX_FD_RFSIMU; buffer_t *ptr = &bridge->buf[buff_index]; bridge->nb_cnx++; - ptr->circularBuf = calloc(1, sampleToByte(CirSize, 1)); + ptr->circularBuf = static_cast(calloc(1, sampleToByte(CirSize, 1))); if (ptr->circularBuf == NULL) { LOG_E(HW, "malloc(%lu) failed\n", sampleToByte(CirSize, 1)); return NULL; @@ -208,7 +210,7 @@ static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock) ptr->channel_model = find_channel_desc_fromname(modelname); // path_loss in dB if (!ptr->channel_model) { // Use legacy method to find channel model - this will use the same channel model for all clients - char *legacy_model_name = (bridge->role == SIMU_ROLE_SERVER) ? "rfsimu_channel_ue0" : "rfsimu_channel_enB0"; + const char *legacy_model_name = (bridge->role == SIMU_ROLE_SERVER) ? "rfsimu_channel_ue0" : "rfsimu_channel_enB0"; ptr->channel_model = find_channel_desc_fromname(legacy_model_name); if (!ptr->channel_model) { LOG_E(HW, "Channel model %s/%s not found, check config file\n", modelname, legacy_model_name); @@ -282,7 +284,7 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) LOG_E(HW, "write() in save iq file failed (%d)\n", errno); } - char *buf = _buf; + char *buf = static_cast(_buf); ssize_t l; while (count) { @@ -384,7 +386,7 @@ static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, if (b->conn_sock >= 0 && (strcmp(b->channel_model->model_name, modelname) == 0)) { channel_desc_t *newmodel = new_channel_desc_scm(t->tx_num_channels, t->rx_num_channels, - channelmod, + static_cast(channelmod), t->sample_rate, t->rx_freq, t->tx_bw, @@ -520,9 +522,9 @@ static int startServer(openair0_device *device) snprintf(port, sizeof(port), "%d", t->port); struct addrinfo hints = { - .ai_family = AF_INET6, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_PASSIVE, + .ai_flags = AI_PASSIVE, + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, }; int s = getaddrinfo(NULL, port, &hints, &results); @@ -619,7 +621,7 @@ static int client_try_connect(const char *host, uint16_t port) static int startClient(openair0_device *device) { - rfsimulator_state_t *t = device->priv; + rfsimulator_state_t *t = static_cast(device->priv); t->role = SIMU_ROLE_CLIENT; int sock; @@ -673,9 +675,9 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, buffer_t *b = &t->buf[i]; if (b->conn_sock >= 0) { - samplesBlockHeader_t header = {nsamps, nbAnt, timestamp}; if (!nbAnt) LOG_E(HW, "rfsimulator sending 0 tx antennas\n"); + samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp}; fullwrite(b->conn_sock, &header, sizeof(header), t); if (nbAnt == 1) { fullwrite(b->conn_sock, samplesVoid[0], sampleToByte(nsamps, nbAnt), t); @@ -712,10 +714,10 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, nsamps, timestamp, timestamp + nsamps, - signal_energy(samplesVoid[0], nsamps)); + signal_energy(static_cast(samplesVoid[0]), nsamps)); /* trace only first antenna */ - T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], sampleToByte(nsamps, 1))); + T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); return nsamps; } @@ -728,7 +730,8 @@ static int rfsimulator_write(openair0_device *device, int flags) { timestamp -= device->openair0_cfg->command_line_sample_advance; - return rfsimulator_write_internal(device->priv, timestamp, samplesVoid, nsamps, nbAnt, flags); + rfsimulator_state_t *t = static_cast(device->priv); + return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, flags); } static bool add_client(rfsimulator_state_t *t) @@ -758,7 +761,7 @@ static bool add_client(rfsimulator_state_t *t) void *samplesVoid[t->tx_num_channels]; for (int i = 0; i < t->tx_num_channels; i++) samplesVoid[i] = (void *)&v; - samplesBlockHeader_t header = {1, t->tx_num_channels, t->lastWroteTS}; + samplesBlockHeader_t header = {(uint32_t)1, (uint32_t)t->tx_num_channels, (uint64_t)t->lastWroteTS}; fullwrite(conn_sock, &header, sizeof(header), t); fullwrite(conn_sock, samplesVoid, sampleToByte(1, t->tx_num_channels), t); @@ -780,7 +783,7 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ b->lastReceivedTS = b->th.timestamp; b->trashingPacket = true; } else { - if (b->lastReceivedTS < b->th.timestamp) { + if (b->lastReceivedTS < (int64_t)b->th.timestamp) { // We have a transmission hole to fill, like TDD // we create no signal samples up to the beginning of this reception int nbAnt = b->th.nbAnt; @@ -798,7 +801,7 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ memset(b->circularBuf, 0, sampleToByte(CirSize, 1)); } b->lastReceivedTS = b->th.timestamp; - } else if (b->lastReceivedTS > b->th.timestamp) { + } else if (b->lastReceivedTS > (int64_t)b->th.timestamp) { LOG_W(HW, "Received data in past: current is %lu, new reception: %lu!\n", b->lastReceivedTS, b->th.timestamp); b->trashingPacket = true; } @@ -850,7 +853,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) } for (int nbEv = 0; nbEv < nfds; ++nbEv) { - buffer_t *b = events[nbEv].data.ptr; + buffer_t *b = static_cast(events[nbEv].data.ptr); if (events[nbEv].events & EPOLLIN && b == NULL) { bool ret = add_client(t); @@ -895,7 +898,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) { - rfsimulator_state_t *t = device->priv; + rfsimulator_state_t *t = static_cast(device->priv); LOG_D(HW, "Enter rfsimulator_read, expect %d samples, will release at TS: %ld, nbAnt %d\n", nsamps, @@ -995,7 +998,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest sample_t *out = (sample_t *)samplesVoid[a_rx]; if (nbAnt_tx == 1 && t->nb_cnx == 1) { // optimized for 1 Tx and 1 UE sample_t *firstSample = (sample_t *)&(ptr->circularBuf[firstIndex]); - if (firstIndex + nsamps > CirSize) { + if ((uint64_t)firstIndex + (uint64_t)nsamps > CirSize) { int tailSz = CirSize - firstIndex; memcpy(out, firstSample, sampleToByte(tailSz, 1)); memcpy(out + tailSz, &ptr->circularBuf[0], sampleToByte(nsamps - tailSz, 1)); @@ -1060,10 +1063,10 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest nsamps, *ptimestamp, t->nextRxTstamp, - signal_energy(samplesVoid[0], nsamps)); + signal_energy(static_cast(samplesVoid[0]), nsamps)); /* trace only first antenna */ - T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], sampleToByte(nsamps, 1))); + T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); return nsamps; } @@ -1075,7 +1078,7 @@ static int rfsimulator_reset_stats(openair0_device *device) { return 0; } static void rfsimulator_end(openair0_device *device) { - rfsimulator_state_t *s = device->priv; + rfsimulator_state_t *s = static_cast(device->priv); for (int i = 0; i < MAX_FD_RFSIMU; i++) { buffer_t *b = &s->buf[i]; if (b->conn_sock >= 0) @@ -1096,7 +1099,7 @@ static int rfsimulator_stop(openair0_device *device) { return 0; } static int rfsimulator_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) { - rfsimulator_state_t *s = device->priv; + rfsimulator_state_t *s = static_cast(device->priv); s->rx_freq = openair0_cfg->rx_freq[0]; return 0; } @@ -1107,11 +1110,11 @@ static int rfsimulator_write_init(openair0_device *device) { return 0; } -__attribute__((__visibility__("default"))) +extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { // to change the log level, use this on command line // --log_config.hw_log_level debug - rfsimulator_state_t *rfsimulator = calloc(sizeof(rfsimulator_state_t), 1); + rfsimulator_state_t *rfsimulator = static_cast(calloc(sizeof(rfsimulator_state_t), 1)); // initialize channel simulation rfsimulator->tx_num_channels = openair0_cfg->tx_num_channels; rfsimulator->rx_num_channels = openair0_cfg->rx_num_channels; From c4b94cff4badd26bdae83815850482ad9f2569e4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 8 Oct 2025 16:03:44 +0200 Subject: [PATCH 03/43] Change trx_write_func to add beams definition over multiple tx antennas per beam Co-authored-by: Laurent Thomas --- radio/COMMON/common_lib.h | 24 +++++++++++++++++++--- radio/rfsimulator/simulator.cpp | 35 +++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index b87a6a8abf..b98c0c82f8 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -491,12 +491,29 @@ struct openair0_device_t { /*! \brief Called to send samples to the RF target @param device pointer to the device structure specific to the RF hardware target @param timestamp The timestamp at whicch the first sample MUST be sent - @param buff Buffer which holds the samples (2 dimensional) + @param buff Buffer which holds the samples (3 dimensional) @param nsamps number of samples to be sent - @param number of antennas + @param nb_antennas_tx number of antennas + @param num_beams number of beams @param flags flags must be set to true if timestamp parameter needs to be applied */ - int (*trx_write_func)(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps,int antenna_id, int flags); + int (*trx_write_beams_func)(openair0_device *device, + openair0_timestamp timestamp, + void ***buff, + int nsamps, + int nb_antennas_tx, + int num_beams, + int flags); + + /*! \brief Called to send samples to the RF target + @param device pointer to the device structure specific to the RF hardware target + @param timestamp The timestamp at whicch the first sample MUST be sent + @param buff Buffer which holds the samples (2 dimensional) + @param nsamps number of samples to be sent + @param nb_antennas_tx number of antennas + @param flags flags must be set to true if timestamp parameter needs to be applied + */ + int (*trx_write_func)(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int nb_antennas_tx, int flags); /*! \brief Called to send samples to the RF target @param device pointer to the device structure specific to the RF hardware target @@ -629,6 +646,7 @@ typedef struct { uint64_t timestamp; // Timestamp value of first sample uint32_t option_value; // Option value uint32_t option_flag; // Option flag + uint64_t beam_map; } samplesBlockHeader_t; #ifdef __cplusplus diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index bb61a48e89..fe3335009d 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -663,9 +663,10 @@ static int startClient(openair0_device *device) static int rfsimulator_write_internal(rfsimulator_state_t *t, openair0_timestamp timestamp, - void **samplesVoid, + void ***samplesVoid, int nsamps, int nbAnt, + uint64_t beam_map, int flags) { mutexlock(t->Sockmutex); @@ -677,14 +678,14 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, if (b->conn_sock >= 0) { if (!nbAnt) LOG_E(HW, "rfsimulator sending 0 tx antennas\n"); - samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp}; + samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beam_map}; fullwrite(b->conn_sock, &header, sizeof(header), t); if (nbAnt == 1) { - fullwrite(b->conn_sock, samplesVoid[0], sampleToByte(nsamps, nbAnt), t); + fullwrite(b->conn_sock, samplesVoid[0][0], sampleToByte(nsamps, nbAnt), t); } else { sample_t tmpSamples[nsamps][nbAnt]; for (int a = 0; a < nbAnt; a++) { - sample_t *in = (sample_t *)samplesVoid[a]; + sample_t *in = (sample_t *)samplesVoid[0][a]; for (int s = 0; s < nsamps; s++) tmpSamples[s][a] = in[s]; } @@ -714,7 +715,7 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, nsamps, timestamp, timestamp + nsamps, - signal_energy(static_cast(samplesVoid[0]), nsamps)); + signal_energy(static_cast(samplesVoid[0][0]), nsamps)); /* trace only first antenna */ T(T_USRP_TX_ANT0, T_INT(timestamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); @@ -722,16 +723,23 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, return nsamps; } -static int rfsimulator_write(openair0_device *device, - openair0_timestamp timestamp, - void **samplesVoid, - int nsamps, - int nbAnt, - int flags) +static int rfsimulator_write_beams(openair0_device *device, + openair0_timestamp timestamp, + void ***samplesVoid, + int nsamps, + int nbAnt, + int num_beams, + int flags) { timestamp -= device->openair0_cfg->command_line_sample_advance; rfsimulator_state_t *t = static_cast(device->priv); - return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, flags); + return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, 1, flags); +} + +static int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int cc, int flags) +{ + void **tmp = buff; + return rfsimulator_write_beams(device, timestamp, &tmp, nsamps, cc, 1, flags); } static bool add_client(rfsimulator_state_t *t) @@ -761,7 +769,7 @@ static bool add_client(rfsimulator_state_t *t) void *samplesVoid[t->tx_num_channels]; for (int i = 0; i < t->tx_num_channels; i++) samplesVoid[i] = (void *)&v; - samplesBlockHeader_t header = {(uint32_t)1, (uint32_t)t->tx_num_channels, (uint64_t)t->lastWroteTS}; + samplesBlockHeader_t header = {1, (uint32_t)t->tx_num_channels, (uint64_t)t->lastWroteTS, 0, 0, 1}; fullwrite(conn_sock, &header, sizeof(header), t); fullwrite(conn_sock, samplesVoid, sampleToByte(1, t->tx_num_channels), t); @@ -1145,6 +1153,7 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { device->trx_set_freq_func = rfsimulator_set_freq; device->trx_set_gains_func = rfsimulator_set_gains; device->trx_write_func = rfsimulator_write; + device->trx_write_beams_func = rfsimulator_write_beams; device->trx_read_func = rfsimulator_read; /* let's pretend to be a b2x0 */ device->type = RFSIMULATOR; From d355e0412d30f07e8bfcd86a4d2b4fbcd21f3745 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 26 Sep 2025 15:07:16 +0200 Subject: [PATCH 04/43] Add prototype for trx_set_beams function This function is to be called by the application to switch beams on reception and transmission. When receiving new samples using trx_read/trx_write the application is expected to be able to determine which beam the samples belong to. --- radio/COMMON/common_lib.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index b98c0c82f8..3fd9d6243d 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -588,6 +588,21 @@ struct openair0_device_t { */ int (*trx_set_gains_func)(openair0_device *device, openair0_config_t *openair0_cfg); + /*! \brief Set tx/rx beams + * + * Set the tx/rx beams. This has to be done in advance of the reception in order to + * allow the underlying device to change receiver configuration. The exact time depends + * on the device. + * + * NOTICE: the samples returned from trx_read_func may belong to more than one beam. It is up + * to the application to determine the beam of the received samples. + * + * \param device the hardware to use + * \param beam_map the beams to receive + * \return 0 on success + */ + int (*trx_set_beams)(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp); + /*! \brief RRU Configuration callback * \param idx RU index * \param arg pointer to capabilities or configuration From 9e434d60a53ad9bafb6a9bfe1291a6a93d62ee98 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 2 Dec 2025 11:44:27 +0100 Subject: [PATCH 05/43] gNB: move downlinkHARQ_FeedbackDisabled_r17 from initial spCellConfig to spCellConfigDedicated This is a preparation for configuring disabled HARQ feedback only for UEs supporting it. --- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index 3cd448b216..a56d6e5ef8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -3517,19 +3517,6 @@ static NR_SpCellConfig_t *get_initial_SpCellConfig(int uid, asn1cCallocOne(pdsch_servingcellconfig->nrofHARQ_ProcessesForPDSCH, NR_PDSCH_ServingCellConfig__nrofHARQ_ProcessesForPDSCH_n16); pdsch_servingcellconfig->pucch_Cell = NULL; set_dl_maxmimolayers(pdsch_servingcellconfig, scc, NULL, configuration->maxMIMO_layers); - if (configuration->disable_harq) { - if (!pdsch_servingcellconfig->ext3) - pdsch_servingcellconfig->ext3 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3)); - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17)); - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->present = NR_SetupRelease_DownlinkHARQ_FeedbackDisabled_r17_PR_setup; - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf = calloc(4, sizeof(uint8_t)); - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.size = 4; - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.bits_unused = 0; - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[0] = 0xFF; - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[1] = 0xFF; - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[2] = 0xFF; - pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[3] = 0xFF; - } uint64_t bitmap = get_ssb_bitmap(scc); int first_active_bwp = 0; @@ -3853,6 +3840,20 @@ void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, NR_PDSCH_ServingCellConfig_t *pdsch_servingcellconfig = spCellConfigDedicated->pdsch_ServingCellConfig->choice.setup; set_dl_maxmimolayers(pdsch_servingcellconfig, scc, uecap, configuration->maxMIMO_layers); + if (configuration->disable_harq) { + if (!pdsch_servingcellconfig->ext3) + pdsch_servingcellconfig->ext3 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3)); + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17 = calloc(1, sizeof(*pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17)); + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->present = NR_SetupRelease_DownlinkHARQ_FeedbackDisabled_r17_PR_setup; + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf = calloc(4, sizeof(uint8_t)); + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.size = 4; + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.bits_unused = 0; + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[0] = 0xFF; + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[1] = 0xFF; + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[2] = 0xFF; + pdsch_servingcellconfig->ext3->downlinkHARQ_FeedbackDisabled_r17->choice.setup.buf[3] = 0xFF; + } + NR_CSI_MeasConfig_t *csi_MeasConfig = spCellConfigDedicated->csi_MeasConfig->choice.setup; for (int report = 0; report < csi_MeasConfig->csi_ReportConfigToAddModList->list.count; report++) { NR_CSI_ReportConfig_t *csirep = csi_MeasConfig->csi_ReportConfigToAddModList->list.array[report]; From 25cd85fa68081b803896776eec4e58db336ab1a7 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 4 Dec 2025 16:44:19 +0100 Subject: [PATCH 06/43] gNB: apply new NR_CellGroupConfig when reconfiguration is complete --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 14 ++++----- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 18 +++++++++-- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 2 +- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 30 ++++++++++--------- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 7 ++--- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index d5c1048ad6..9832f0332b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -595,17 +595,17 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac, static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE) { - if (UE->reconfigSpCellConfig) { - // in case of reestablishment, the spCellConfig had to be released - // temporarily. Reapply now before doing the reconfiguration. - UE->CellGroup->spCellConfig = UE->reconfigSpCellConfig; - // UE->reconfigSpCellConfig to be NULLed after receiving reconfiguration complete + if (!UE->reconfigCellGroup) { + LOG_W(NR_MAC, "Received ACK for RRCReconfiguration, but nothing to apply!\n"); + return; } + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); + UE->CellGroup = UE->reconfigCellGroup; + UE->reconfigCellGroup = NULL; NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; /* clean BWP structures */ clean_bwp_structures(UE->CellGroup->spCellConfig); configure_UE_BWP(mac, scc, UE, false, NR_SearchSpace__searchSpaceType_PR_common, -1, -1); - UE->await_reconfig = false; } typedef struct UEsched_s { @@ -885,7 +885,7 @@ static void pf_dl(gNB_MAC_INST *mac, sched_pdsch.action = NULL; int srb1 = 1; /* everything that's only 3 bytes is an ack. To be safe, use a bit more. */ - if (iterator->UE->await_reconfig && sched_ctrl->rlc_status[srb1].bytes_in_buffer > 10) + if (iterator->UE->reconfigCellGroup && sched_ctrl->rlc_status[srb1].bytes_in_buffer > 10) sched_pdsch.action = ack_reconfig; // Fix me: currently, the RLC does not give us the total number of PDUs diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 14dedb1de9..5e611e4150 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -2488,7 +2488,7 @@ NR_UE_info_t *find_ra_UE(NR_UEs_t *UEs, rnti_t rntiP) void delete_nr_ue_data(NR_UE_info_t *UE, NR_COMMON_channels_t *ccPtr, uid_allocator_t *uia) { ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); - ASN_STRUCT_FREE(asn_DEF_NR_SpCellConfig, UE->reconfigSpCellConfig); + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; seq_arr_free(&sched_ctrl->lc_config, NULL); @@ -2852,6 +2852,20 @@ void configure_UE_BWP(gNB_MAC_INST *nr_mac, sc_info->nrofHARQ_ProcessesForPUSCH_r17 = pusch_servingcellconfig->ext3->nrofHARQ_ProcessesForPUSCH_r17; } } + } else { + sc_info->csi_MeasConfig = NULL; + sc_info->nrofHARQ_ProcessesForPDSCH = NULL; + sc_info->maxMIMO_Layers_PDSCH = NULL; + sc_info->downlinkHARQ_FeedbackDisabled_r17 = NULL; + sc_info->pdsch_CGB_Transmission = NULL; + sc_info->nrofHARQ_ProcessesForPDSCH_v1700 = NULL; + sc_info->crossCarrierSchedulingConfig = NULL; + sc_info->supplementaryUplink = NULL; + sc_info->carrierSwitching = NULL; + sc_info->maxMIMO_Layers_PUSCH = NULL; + sc_info->rateMatching_PUSCH = NULL; + sc_info->pusch_CGB_Transmission = NULL; + sc_info->nrofHARQ_ProcessesForPUSCH_r17 = NULL; } if (CellGroup && CellGroup->physicalCellGroupConfig) @@ -3919,7 +3933,7 @@ void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, NR_UE_info_t *UE, buf, sizeof(buf)); AssertFatal(enc_rval.encoded > 0, "ASN1 encoding of CellGroupConfig failed, failed type %s\n", enc_rval.failed_type->name); - UE->await_reconfig = true; + UE->reestablish_rlc = true; du_to_cu_rrc_information_t du2cu = { .cellGroupConfig = buf, .cellGroupConfig_length = (enc_rval.encoded + 7) >> 3, diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index abe5cd8361..0e97ea7a6c 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -882,7 +882,7 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, // Only trigger RRCReconfiguration if UE is not performing RRCReestablishment // The RRCReconfiguration will be triggered by the RRCReestablishmentComplete - if (!old_UE->reconfigSpCellConfig) { + if (!old_UE->reconfigCellGroup) { LOG_I(NR_MAC, "Received UL_SCH_LCID_C_RNTI with C-RNTI 0x%04x, triggering RRC Reconfiguration\n", crnti); // Trigger RRCReconfiguration nr_mac_trigger_reconfiguration(mac, old_UE, -1); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 6d105078b7..4fbc637355 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -695,8 +695,8 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) cgc.len = (enc_rval.encoded + 7) >> 3; resp.du_to_cu_rrc_info.cell_group_config = cgc; - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); - UE->CellGroup = new_CellGroup; + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); + UE->reconfigCellGroup = new_CellGroup; int ss_type = cg_configinfo ? NR_SearchSpace__searchSpaceType_PR_ue_Specific: NR_SearchSpace__searchSpaceType_PR_common; configure_UE_BWP(mac, scc, UE, false, ss_type, -1, -1); @@ -770,19 +770,19 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) "RRC reconfiguration outcome unsuccessful, but no rollback mechanism implemented to come back to old configuration\n"); } else if (req->reconfig_compl) { LOG_I(NR_MAC, "DU received confirmation of successful RRC Reconfiguration\n"); - if (UE->reconfigSpCellConfig) { - if (UE->await_reconfig) { - // in case of reestablishment, the spCellConfig had to be released - // temporarily. Reapply now before doing the reconfiguration. - UE->CellGroup->spCellConfig = UE->reconfigSpCellConfig; - UE->await_reconfig = false; - } - UE->reconfigSpCellConfig = NULL; + if (UE->reconfigCellGroup) { + LOG_W(NR_MAC, "reconfigCellGroup still present, did we miss ACK for RRCReconfiguration?\n"); + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); + UE->CellGroup = UE->reconfigCellGroup; + UE->reconfigCellGroup = NULL; + } + if (UE->reestablish_rlc) { for (int i = 1; i < seq_arr_size(&UE->UE_sched_ctrl.lc_config); ++i) { nr_lc_config_t *c = seq_arr_at(&UE->UE_sched_ctrl.lc_config, i); c->suspended = false; nr_rlc_reestablish_entity(req->gNB_DU_ue_id, c->lcid); } + UE->reestablish_rlc = false; } // we re-configure the BWP to apply the CellGroup and to use UE specific Search Space with DCIX1 configure_UE_BWP(mac, scc, UE, false, NR_SearchSpace__searchSpaceType_PR_ue_Specific, -1, -1); @@ -809,8 +809,8 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) cgc.len = (enc_rval.encoded + 7) >> 3; resp.du_to_cu_rrc_info->cell_group_config = cgc; - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); - UE->CellGroup = new_CellGroup; + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); + UE->reconfigCellGroup = new_CellGroup; configure_UE_BWP(mac, scc, UE, false, NR_SearchSpace__searchSpaceType_PR_common, -1, -1); } else { ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, new_CellGroup); // we actually don't need it @@ -966,6 +966,7 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); UE->CellGroup = oldUE->CellGroup; oldUE->CellGroup = NULL; + ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); UE->capability = oldUE->capability; oldUE->capability = NULL; UE->mac_stats = oldUE->mac_stats; @@ -973,9 +974,10 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) /* 38.331 5.3.7.2 says that the UE releases the spCellConfig, so we drop it * from the current configuration. It will be reapplied when the * reconfiguration has succeeded (indicated by the CU) */ - UE->reconfigSpCellConfig = UE->CellGroup->spCellConfig; + asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&UE->reconfigCellGroup, UE->CellGroup); + ASN_STRUCT_FREE(asn_DEF_NR_SpCellConfig, UE->CellGroup->spCellConfig); UE->CellGroup->spCellConfig = NULL; - UE->await_reconfig = true; + UE->reestablish_rlc = true; mac_remove_nr_ue(mac, *dl_rrc->old_gNB_DU_ue_id); pthread_mutex_unlock(&mac->sched_lock); nr_rlc_remove_ue(dl_rrc->gNB_DU_ue_id); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 9035901456..133606e6e9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -788,9 +788,8 @@ typedef struct NR_UE_info { NR_mac_stats_t mac_stats; /// currently active CellGroupConfig NR_CellGroupConfig_t *CellGroup; - /// in case of reestablishment, old spCellConfig to apply after - /// reconfiguration - NR_SpCellConfig_t *reconfigSpCellConfig; + /// in case of reconfiguration, new CellConfig to apply + NR_CellGroupConfig_t *reconfigCellGroup; NR_UE_NR_Capability_t *capability; measgap_config_t measgap_config; // UE selected beam index @@ -799,7 +798,7 @@ typedef struct NR_UE_info { float dl_thr_ue; long pdsch_HARQ_ACK_Codebook; bool is_redcap; - bool await_reconfig; + bool reestablish_rlc; NR_RA_t *ra; // 3GPP mandates that BWPs are enumerated consecutively, but we only send one (dedicated) // BWP to the UE (and modify that BWP on reconfiguration); consequently, the BWP ID for a From 21722a9fa11a6a70b329919e31801a0ad2d67bcf Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 11 Dec 2025 16:50:44 +0100 Subject: [PATCH 07/43] gNB: fix use after free of old UE->sc_info.csi_MeasConfig --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index eb32fde2e6..1f81ba249e 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -977,11 +977,6 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c return; } - NR_CSI_MeasConfig_t *csi_MeasConfig = UE->sc_info.csi_MeasConfig; - if (csi_MeasConfig == NULL) { - NR_SCHED_UNLOCK(&nrmac->sched_lock); - return; - } NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; // tpc (power control) @@ -1017,8 +1012,11 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c if ((uci_234->pduBitmap >> 2) & 0x01) { LOG_D(NR_MAC, "CSI CRC %d\n", uci_234->csi_part1.csi_part1_crc); if (uci_234->csi_part1.csi_part1_crc != 1) { - // API to parse the csi report and store it into sched_ctrl - extract_pucch_csi_report(csi_MeasConfig, uci_234, frame, slot, UE, nrmac); + NR_CSI_MeasConfig_t *csi_MeasConfig = UE->sc_info.csi_MeasConfig; + if (csi_MeasConfig != NULL) { + // API to parse the csi report and store it into sched_ctrl + extract_pucch_csi_report(csi_MeasConfig, uci_234, frame, slot, UE, nrmac); + } } free(uci_234->csi_part1.csi_part1_payload); } From e74792586f61e3f6a08f82d8395e0eb37b80f0b8 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Wed, 17 Dec 2025 18:05:32 +0100 Subject: [PATCH 08/43] fix BWP switching Co-authored-by: Luis Pereira --- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 5 ++--- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 17 +++++------------ openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h | 1 - 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 5e611e4150..0f614e53f8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3933,7 +3933,8 @@ void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, NR_UE_info_t *UE, buf, sizeof(buf)); AssertFatal(enc_rval.encoded > 0, "ASN1 encoding of CellGroupConfig failed, failed type %s\n", enc_rval.failed_type->name); - UE->reestablish_rlc = true; + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); + UE->reconfigCellGroup = cellGroup_for_UE; du_to_cu_rrc_information_t du2cu = { .cellGroupConfig = buf, .cellGroupConfig_length = (enc_rval.encoded + 7) >> 3, @@ -3947,8 +3948,6 @@ void nr_mac_trigger_reconfiguration(const gNB_MAC_INST *nrmac, NR_UE_info_t *UE, .cause_value = F1AP_CauseRadioNetwork_action_desirable_for_radio_reasons, }; nrmac->mac_rrc.ue_context_modification_required(&required); - if (cellGroup_for_UE) - free_cellGroupConfig(cellGroup_for_UE); } long get_lcid_from_drbid(int drb_id) diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index a56d6e5ef8..efe6fe1391 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -3813,14 +3813,12 @@ NR_CellGroupConfig_t *update_cellGroupConfig_for_BWP_switch(NR_CellGroupConfig_t // we temporarily need to keep both the old and the new BWP in the CG used by the gNB // while removing the old from the CG sent to the UE - if (old_bwp > 0) { - NR_CellGroupConfig_t *clone_cg = NULL; - const int copy_result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&clone_cg, cellGroupConfig); - AssertFatal(copy_result == 0, "unable to copy NR_CellGroupConfig for cloning\n"); + NR_CellGroupConfig_t *clone_cg = NULL; + const int copy_result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&clone_cg, cellGroupConfig); + AssertFatal(copy_result == 0, "unable to copy NR_CellGroupConfig for cloning\n"); + if (old_bwp > 0) clean_bwp_structures(clone_cg->spCellConfig); - return clone_cg; - } - return NULL; + return clone_cg; } void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, @@ -3933,11 +3931,6 @@ void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, } } -void free_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig) -{ - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, cellGroupConfig); -} - int encode_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, uint8_t *buffer, int max_buffer_size) { asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, cellGroupConfig, buffer, max_buffer_size); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h index b776d667d6..27cca60b26 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.h @@ -96,7 +96,6 @@ void update_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, const NR_UE_NR_Capability_t *uecap, const nr_mac_config_t *configuration, const NR_ServingCellConfigCommon_t *scc); -void free_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig); int encode_cellGroupConfig(NR_CellGroupConfig_t *cellGroupConfig, uint8_t *buffer, int max_buffer_size); NR_CellGroupConfig_t *decode_cellGroupConfig(const uint8_t *buffer, int max_buffer_size); From 0a0dfae33311bc551e4d56253f850c745e038a18 Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Thu, 18 Dec 2025 09:57:10 +0000 Subject: [PATCH 09/43] Restore previous 'local_bwp_id' on RRCReestablishment --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 4fbc637355..20b30cb074 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -971,6 +971,7 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) oldUE->capability = NULL; UE->mac_stats = oldUE->mac_stats; UE->measgap_config = oldUE->measgap_config; + UE->local_bwp_id = oldUE->local_bwp_id; /* 38.331 5.3.7.2 says that the UE releases the spCellConfig, so we drop it * from the current configuration. It will be reapplied when the * reconfiguration has succeeded (indicated by the CU) */ From b59bcaf49453e9ab6f7d5615a8d3d50f9a739110 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 23 Sep 2025 14:07:14 +0200 Subject: [PATCH 10/43] NR UE: provide velocity vector down to nr-ue.c, preparing for Doppler calculation --- executables/nr-ue.c | 43 ++++++++++++++----- .../nfapi/public_inc/fapi_nr_ue_interface.h | 8 +++- openair1/SCHED_NR_UE/fapi_nr_ue_l1.c | 2 + openair2/LAYER2/NR_MAC_UE/config_ue.c | 42 ++++++++++++++---- openair2/LAYER2/NR_MAC_UE/mac_defs.h | 8 +++- openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c | 2 + 6 files changed, 81 insertions(+), 24 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index f57b663f1e..a1d7ef2888 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -732,15 +732,32 @@ static inline void apply_ntn_timing_advance(PHY_VARS_NR_UE *UE, const NR_DL_FRAM + ntn_config_params->epoch_hfn * 10240; const int ms_since_epoch = abs_subframe_tx - abs_subframe_epoch; - const double omega = ntn_config_params->omega; - const double cos_wt = cos(omega * ms_since_epoch); - const double sin_wt = sin(omega * ms_since_epoch); - const position_t pos_sat_0 = ntn_config_params->pos_sat_0; - const position_t pos_sat_90 = ntn_config_params->pos_sat_90; - const position_t pos_sat = {pos_sat_0.X * cos_wt + pos_sat_90.X * sin_wt, - pos_sat_0.Y * cos_wt + pos_sat_90.Y * sin_wt, - pos_sat_0.Z * cos_wt + pos_sat_90.Z * sin_wt}; + const position_t vel_sat_0 = ntn_config_params->vel_sat_0; + + position_t pos_sat; + position_t vel_sat; + + const double omega = ntn_config_params->omega; + if (omega) { + const double cos_wt = cos(omega * ms_since_epoch); + const double sin_wt = sin(omega * ms_since_epoch); + + const position_t pos_sat_90 = ntn_config_params->pos_sat_90; + pos_sat = (position_t){pos_sat_0.X * cos_wt + pos_sat_90.X * sin_wt, + pos_sat_0.Y * cos_wt + pos_sat_90.Y * sin_wt, + pos_sat_0.Z * cos_wt + pos_sat_90.Z * sin_wt}; + + const position_t vel_sat_90 = ntn_config_params->vel_sat_90; + vel_sat = (position_t){vel_sat_0.X * cos_wt + vel_sat_90.X * sin_wt, + vel_sat_0.Y * cos_wt + vel_sat_90.Y * sin_wt, + vel_sat_0.Z * cos_wt + vel_sat_90.Z * sin_wt}; + } else { + pos_sat = (position_t){pos_sat_0.X + vel_sat_0.X * ms_since_epoch / 1000, + pos_sat_0.Y + vel_sat_0.Y * ms_since_epoch / 1000, + pos_sat_0.Z + vel_sat_0.Z * ms_since_epoch / 1000}; + vel_sat = vel_sat_0; + } position_t pos_ue = {0}; get_position_coordinates(UE->Mod_id, &pos_ue); @@ -751,6 +768,9 @@ static inline void apply_ntn_timing_advance(PHY_VARS_NR_UE *UE, const NR_DL_FRAM // calculate distance between SAT and UE const double distance = sqrt(dir_sat_ue.X * dir_sat_ue.X + dir_sat_ue.Y * dir_sat_ue.Y + dir_sat_ue.Z * dir_sat_ue.Z); + // calculate projected velocity from SAT towards UE + const double vel_sat_ue = (vel_sat.X * dir_sat_ue.X + vel_sat.Y * dir_sat_ue.Y + vel_sat.Z * dir_sat_ue.Z) / distance; + // calculate round-trip-time (factor 2) between SAT and UE in ms (factor 1000) const double N_UE_TA_adj = 2000 * distance / SPEED_OF_LIGHT; @@ -764,14 +784,15 @@ static inline void apply_ntn_timing_advance(PHY_VARS_NR_UE *UE, const NR_DL_FRAM * fp->samples_per_subframe; LOG_D(PHY, - "N_UE_TA_adj = %f ms, N_common_ta_adj = %f ms, N_common_ta_drift = %f µs/s, N_common_ta_drift_variant = %f µs/s², ms_since_epoch = %d ms, " - "computed timing_advance_ntn = %d samples\n", + "N_UE_TA_adj = %f ms, N_common_ta_adj = %f ms, N_common_ta_drift = %f µs/s, N_common_ta_drift_variant = %f µs/s², " + "ms_since_epoch = %d ms, computed timing_advance_ntn = %d samples, satellite velocity towards UE = %f m/s\n", N_UE_TA_adj, N_common_ta_adj, N_common_ta_drift, N_common_ta_drift_variant, ms_since_epoch, - UE->timing_advance_ntn); + UE->timing_advance_ntn, + vel_sat_ue); } static inline void apply_ntn_config(PHY_VARS_NR_UE *UE, diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index 37337e82d3..49752be093 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -569,10 +569,14 @@ typedef struct { // orbital angular velocity in rad/ms double omega; - // satellite position at epoch time + // satellite position vector at epoch time position_t pos_sat_0; - // satellite position at 90° orbit + // satellite position vector at 90° orbit position_t pos_sat_90; + // satellite velocity vector at epoch time + position_t vel_sat_0; + // satellite velocity vector at 90° orbit + position_t vel_sat_90; // N_common_ta_adj represents common round-trip-time between gNB and SAT received in SIB19 (ms) double N_common_ta_adj; diff --git a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c index 5c679630f4..92a0b043e7 100644 --- a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c +++ b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c @@ -102,6 +102,8 @@ static void configure_ntn_params(PHY_VARS_NR_UE *ue, fapi_nr_dl_ntn_config_comma ue->ntn_config_message->ntn_config_params.omega = ntn_params_message->omega; ue->ntn_config_message->ntn_config_params.pos_sat_0 = ntn_params_message->pos_sat_0; ue->ntn_config_message->ntn_config_params.pos_sat_90 = ntn_params_message->pos_sat_90; + ue->ntn_config_message->ntn_config_params.vel_sat_0 = ntn_params_message->vel_sat_0; + ue->ntn_config_message->ntn_config_params.vel_sat_90 = ntn_params_message->vel_sat_90; ue->ntn_config_message->ntn_config_params.N_common_ta_adj = ntn_params_message->N_common_ta_adj; ue->ntn_config_message->ntn_config_params.N_common_ta_drift = ntn_params_message->N_common_ta_drift; diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index c92c8225f6..b14097a3ec 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -279,29 +279,49 @@ static void prepare_ue_sat_ta(const NR_PositionVelocity_r17_t *sat_pos, ntn_timi const double vel_sat_2 = vel_sat.X * vel_sat.X + vel_sat.Y * vel_sat.Y + vel_sat.Z * vel_sat.Z; const double vel_mag = sqrt(vel_sat_2); - // calculate angular velocity in rad/ms - const double omega = vel_mag / (radius * 1000); + double omega; // angular velocity in rad/ms + position_t pos_sat_90; // sat position vector in 90° orbit + position_t vel_sat_90; // sat velocity vector in 90° orbit - // calculate sat position in 90° orbit - position_t pos_sat_90 = pos_sat; - if (vel_mag) { - const double scaling = radius / vel_mag; + // assuming circular orbit when satellite moves faster than 1000 m/s + // 1000 m/s are chosen because according to Wikipedia, this seems to be a reasonable minimal + // orbital velocity: https://en.wikipedia.org/wiki/Orbital_speed#Tangential_velocities_at_altitude + if (vel_mag > 1000) { + omega = vel_mag / (radius * 1000); + double scaling = radius / vel_mag; pos_sat_90 = (position_t){vel_sat.X * scaling, vel_sat.Y * scaling, vel_sat.Z * scaling}; + scaling = -vel_mag / radius; + vel_sat_90 = (position_t){pos_sat.X * scaling, pos_sat.Y * scaling, pos_sat.Z * scaling}; + } else { + omega = 0; + pos_sat_90 = pos_sat; + vel_sat_90 = vel_sat; } LOG_I(NR_MAC, - "Satellite angular velocity = %e rad/ms, sat_pos = {%f, %f, %f}, sat_pos_90 = {%f, %f, %f}\n", - omega, + "Satellite orbital radius %f m, pos_sat_0 = {%f, %f, %f}, pos_sat_90 = {%f, %f, %f}\n" + "Satellite velocity %f m/s, angular velocity = %e rad/ms, vel_sat_0 = {%f, %f, %f}, vel_sat_90 = {%f, %f, %f}\n", + radius, pos_sat.X, pos_sat.Y, pos_sat.Z, pos_sat_90.X, pos_sat_90.Y, - pos_sat_90.Z); + pos_sat_90.Z, + vel_mag, + omega, + vel_sat.X, + vel_sat.Y, + vel_sat.Z, + vel_sat_90.X, + vel_sat_90.Y, + vel_sat_90.Z); ntn_ta->omega = omega; ntn_ta->pos_sat_0 = pos_sat; ntn_ta->pos_sat_90 = pos_sat_90; + ntn_ta->vel_sat_0 = vel_sat; + ntn_ta->vel_sat_90 = vel_sat_90; } // populate ntn_ta structure from mac @@ -332,11 +352,15 @@ static void configure_ntn_ta(ntn_timing_advance_componets_t *ntn_ta, const NR_NT ntn_ta->omega = 0; ntn_ta->pos_sat_0 = (position_t){0, 0, 0}; ntn_ta->pos_sat_90 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_0 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; } } else { // Need R - Release if not present ntn_ta->omega = 0; ntn_ta->pos_sat_0 = (position_t){0, 0, 0}; ntn_ta->pos_sat_90 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_0 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; } // handle cellSpecificKoffset_r17 diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index 7fda4a4b6c..42b616c188 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -552,10 +552,14 @@ typedef struct ntn_timing_advance_components { // orbital angular velocity in rad/ms double omega; - // satellite position at epoch time + // satellite position vector at epoch time position_t pos_sat_0; - // satellite position at 90° orbit + // satellite position vector at 90° orbit position_t pos_sat_90; + // satellite velocity vector at epoch time + position_t vel_sat_0; + // satellite velocity vector at 90° orbit + position_t vel_sat_90; // N_common_ta_adj represents common round-trip-time between gNB and SAT received in SIB19 (ms) double N_common_ta_adj; diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c index c8d40e7f11..f7a763f41a 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c @@ -2855,6 +2855,8 @@ static void schedule_ntn_config_command(fapi_nr_dl_config_request_t *dl_config, ntn_config_command_pdu->omega = mac->ntn_ta.omega; ntn_config_command_pdu->pos_sat_0 = mac->ntn_ta.pos_sat_0; ntn_config_command_pdu->pos_sat_90 = mac->ntn_ta.pos_sat_90; + ntn_config_command_pdu->vel_sat_0 = mac->ntn_ta.vel_sat_0; + ntn_config_command_pdu->vel_sat_90 = mac->ntn_ta.vel_sat_90; ntn_config_command_pdu->N_common_ta_adj = mac->ntn_ta.N_common_ta_adj; ntn_config_command_pdu->N_common_ta_drift = mac->ntn_ta.N_common_ta_drift; From bc81662c2a7b31c0b0597b3941fed0ea42dee59d Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 23 Sep 2025 17:22:30 +0200 Subject: [PATCH 11/43] NR UE: consider computed DL/UL Doppler shift in continuous FO compensation --- executables/nr-ue.c | 77 +++++++++++++++++++-------- openair1/PHY/MODULATION/slot_fep_nr.c | 2 +- openair1/PHY/defs_nr_UE.h | 6 ++- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index a1d7ef2888..f3769096f8 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -258,7 +258,7 @@ static void UE_synch(void *arg) { + round((float)((ret.rx_offset << 1) % fp->samples_per_subframe) / fp->samples_per_slot0); if (get_nrUE_params()->cont_fo_comp) { - UE->freq_offset = freq_offset; + UE->freq_offset = freq_offset - UE->dl_Doppler_shift; } else { // rerun with new cell parameters and frequency-offset nr_rf_card_config_freq(cfg0, ul_carrier, dl_carrier, freq_offset); @@ -400,7 +400,12 @@ static void RU_write(nr_rxtx_thread_data_t *rxtxD, bool sl_tx_action, c16_t **tx if (get_nrUE_params()->cont_fo_comp == 2) // different from LO frequency error compensation, Doppler UL pre-compensation has to be negative ul_freq_offset = -ul_freq_offset; for (int i = 0; i < fp->nb_antennas_tx; i++) - nr_fo_compensation(ul_freq_offset, fp->samples_per_subframe, writeTimestamp, txp[i], txp[i], writeBlockSize); + nr_fo_compensation(UE->ul_Doppler_shift + ul_freq_offset, + fp->samples_per_subframe, + writeTimestamp, + txp[i], + txp[i], + writeBlockSize); } int tmp = openair0_write_reorder(&UE->rfdevice, writeTimestamp, (void **)txp, writeBlockSize, fp->nb_antennas_tx, flags); @@ -723,10 +728,18 @@ static inline int get_readBlockSize(uint16_t slot, NR_DL_FRAME_PARMS *fp) { } #define SPEED_OF_LIGHT 299792458 -static inline void apply_ntn_timing_advance(PHY_VARS_NR_UE *UE, const NR_DL_FRAME_PARMS *fp, int abs_subframe_tx) +static void apply_ntn_timing_advance_and_doppler(PHY_VARS_NR_UE *UE, const NR_DL_FRAME_PARMS *fp, int abs_subframe_tx) { const fapi_nr_dl_ntn_config_command_pdu *ntn_config_params = &UE->ntn_config_message->ntn_config_params; + // Handle terrestrial networks + if (ntn_config_params->cell_specific_k_offset == 0) { + UE->timing_advance_ntn = 0; + UE->dl_Doppler_shift = 0; + UE->ul_Doppler_shift = 0; + return; + } + const int abs_subframe_epoch = ntn_config_params->epoch_subframe + ntn_config_params->epoch_sfn * 10 + ntn_config_params->epoch_hfn * 10240; @@ -771,6 +784,12 @@ static inline void apply_ntn_timing_advance(PHY_VARS_NR_UE *UE, const NR_DL_FRAM // calculate projected velocity from SAT towards UE const double vel_sat_ue = (vel_sat.X * dir_sat_ue.X + vel_sat.Y * dir_sat_ue.Y + vel_sat.Z * dir_sat_ue.Z) / distance; + // calculate DL Doppler shift (moving source) + const double dl_Doppler_shift = (vel_sat_ue / (SPEED_OF_LIGHT - vel_sat_ue)) * fp->dl_CarrierFreq; + + // calculate UL Doppler shift (moving target) + const double ul_Doppler_shift = (vel_sat_ue / SPEED_OF_LIGHT) * fp->ul_CarrierFreq; + // calculate round-trip-time (factor 2) between SAT and UE in ms (factor 1000) const double N_UE_TA_adj = 2000 * distance / SPEED_OF_LIGHT; @@ -783,32 +802,33 @@ static inline void apply_ntn_timing_advance(PHY_VARS_NR_UE *UE, const NR_DL_FRAM + N_common_ta_drift_variant * ((int64_t)ms_since_epoch * ms_since_epoch) / 1e9) * fp->samples_per_subframe; + UE->freq_offset -= dl_Doppler_shift - UE->dl_Doppler_shift; + UE->dl_Doppler_shift = dl_Doppler_shift; + UE->ul_Doppler_shift = ul_Doppler_shift; + LOG_D(PHY, - "N_UE_TA_adj = %f ms, N_common_ta_adj = %f ms, N_common_ta_drift = %f µs/s, N_common_ta_drift_variant = %f µs/s², " - "ms_since_epoch = %d ms, computed timing_advance_ntn = %d samples, satellite velocity towards UE = %f m/s\n", - N_UE_TA_adj, - N_common_ta_adj, - N_common_ta_drift, - N_common_ta_drift_variant, - ms_since_epoch, - UE->timing_advance_ntn, - vel_sat_ue); + "satellite velocity towards UE = %f m/s, DL Doppler shift = %f kHz, UL Doppler shift = %f kHz\n", + vel_sat_ue, + dl_Doppler_shift / 1000, + ul_Doppler_shift / 1000); } -static inline void apply_ntn_config(PHY_VARS_NR_UE *UE, - NR_DL_FRAME_PARMS *fp, - int hfn_rx, - int frame_rx, - int slot_rx, - int *duration_rx_to_tx, - int *timing_advance, - int *ntn_koffset) +static void apply_ntn_config(PHY_VARS_NR_UE *UE, + NR_DL_FRAME_PARMS *fp, + int hfn_rx, + int frame_rx, + int slot_rx, + int *duration_rx_to_tx, + int *timing_advance, + int *ntn_koffset) { if (UE->ntn_config_message->update) { UE->ntn_config_message->update = false; + const fapi_nr_dl_ntn_config_command_pdu *ntn_config_params = &UE->ntn_config_message->ntn_config_params; + const int mu = fp->numerology_index; - const int koffset = UE->ntn_config_message->ntn_config_params.cell_specific_k_offset; + const int koffset = ntn_config_params->cell_specific_k_offset; *duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX + (koffset << mu); if (koffset > *ntn_koffset) @@ -818,7 +838,18 @@ static inline void apply_ntn_config(PHY_VARS_NR_UE *UE, *ntn_koffset = koffset; const int abs_subframe_tx = 10240 * hfn_rx + 10 * frame_rx + ((slot_rx + *duration_rx_to_tx) >> mu); - apply_ntn_timing_advance(UE, fp, abs_subframe_tx); + apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); + + LOG_I(PHY, + "k_offset: %dms, N_Common_Ta: %fms, drift: %fµs/s, variant: %fµs/s², " + "timing_advance_ntn: %d samples, DL Doppler shift: %fkHz, UL Doppler shift: %fkHz\n", + koffset << mu, + ntn_config_params->N_common_ta_adj, + ntn_config_params->N_common_ta_drift, + ntn_config_params->N_common_ta_drift_variant, + UE->timing_advance_ntn, + UE->dl_Doppler_shift / 1000, + UE->ul_Doppler_shift / 1000); } } @@ -1050,7 +1081,7 @@ void *UE_thread(void *arg) // Calculate new TA based on SIB19 information for each subframe in NTN mode, if "autonomous_ta" is not enabled if (ntn_koffset && !get_nrUE_params()->autonomous_ta && (absolute_slot + duration_rx_to_tx) % fp->slots_per_subframe == 0) { const int abs_subframe_tx = (absolute_slot + duration_rx_to_tx) / fp->slots_per_subframe; - apply_ntn_timing_advance(UE, fp, abs_subframe_tx); + apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); } const int readBlockSize = get_readBlockSize(slot_nr, fp) - iq_shift_to_apply; diff --git a/openair1/PHY/MODULATION/slot_fep_nr.c b/openair1/PHY/MODULATION/slot_fep_nr.c index 9045e50cb0..58e47fc13a 100644 --- a/openair1/PHY/MODULATION/slot_fep_nr.c +++ b/openair1/PHY/MODULATION/slot_fep_nr.c @@ -119,7 +119,7 @@ int nr_slot_fep(PHY_VARS_NR_UE *ue, if (ue && ue->cont_fo_comp) { start_meas_nr_ue_phy(ue, RX_FO_COMPENSATION_STATS); - nr_fo_compensation(ue->freq_offset, + nr_fo_compensation(ue->dl_Doppler_shift + ue->freq_offset, frame_parms->samples_per_subframe, rx_offset, rxdata_symb_ptr[aa], diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index f266d7aad5..5e2e169c1a 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -462,8 +462,10 @@ typedef struct PHY_VARS_NR_UE_s { double initial_fo; /// initial frequency offset provided by the user int cont_fo_comp; /// flag enabling the continuous frequency offset estimation and compensation - double freq_offset; /// currently compensated frequency offset - double freq_off_acc; /// accumulated frequency error (for PI controller) + double freq_offset; /// currently compensated DL frequency offset (without dl_Doppler_shift) + double freq_off_acc; /// accumulated DL frequency error (for PI controller) + double dl_Doppler_shift; /// calculated DL Doppler shift + double ul_Doppler_shift; /// calculated UL Doppler shift /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB From ae12190a9e573588d64c3ae74c097d9c441e55dd Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Wed, 24 Sep 2025 14:17:33 +0200 Subject: [PATCH 12/43] NR UE: improve log output in MAC --- openair2/LAYER2/NR_MAC_UE/config_ue.c | 62 ++++++++++++++------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index b14097a3ec..ded7c2a754 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -299,15 +299,17 @@ static void prepare_ue_sat_ta(const NR_PositionVelocity_r17_t *sat_pos, ntn_timi } LOG_I(NR_MAC, - "Satellite orbital radius %f m, pos_sat_0 = {%f, %f, %f}, pos_sat_90 = {%f, %f, %f}\n" - "Satellite velocity %f m/s, angular velocity = %e rad/ms, vel_sat_0 = {%f, %f, %f}, vel_sat_90 = {%f, %f, %f}\n", + "Satellite orbital radius %f m, pos_sat_0 = {%f, %f, %f}, pos_sat_90 = {%f, %f, %f}\n", radius, pos_sat.X, pos_sat.Y, pos_sat.Z, pos_sat_90.X, pos_sat_90.Y, - pos_sat_90.Z, + pos_sat_90.Z); + + LOG_I(NR_MAC, + "Satellite velocity %f m/s, angular velocity = %e rad/ms, vel_sat_0 = {%f, %f, %f}, vel_sat_90 = {%f, %f, %f}\n", vel_mag, omega, vel_sat.X, @@ -340,29 +342,6 @@ static void configure_ntn_ta(ntn_timing_advance_componets_t *ntn_ta, const NR_NT ntn_ta->epoch_sfn = epoch_time_r17->sfn_r17; ntn_ta->epoch_subframe = epoch_time_r17->subFrameNR_r17; - // handle ephemerisInfo_r17 - const NR_EphemerisInfo_r17_t *ephemeris_info = ntn_Config_r17->ephemerisInfo_r17; - if (ephemeris_info) { - if (ephemeris_info->present == NR_EphemerisInfo_r17_PR_positionVelocity_r17) { - const NR_PositionVelocity_r17_t *position_velocity = ephemeris_info->choice.positionVelocity_r17; - AssertFatal(position_velocity, "position_velocity should not be NULL here\n"); - prepare_ue_sat_ta(position_velocity, ntn_ta); - } else { - LOG_W(NR_MAC, "NR UE currently supports only ephemerisInfo_r17 of type positionVelocity_r17\n"); - ntn_ta->omega = 0; - ntn_ta->pos_sat_0 = (position_t){0, 0, 0}; - ntn_ta->pos_sat_90 = (position_t){0, 0, 0}; - ntn_ta->vel_sat_0 = (position_t){0, 0, 0}; - ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; - } - } else { // Need R - Release if not present - ntn_ta->omega = 0; - ntn_ta->pos_sat_0 = (position_t){0, 0, 0}; - ntn_ta->pos_sat_90 = (position_t){0, 0, 0}; - ntn_ta->vel_sat_0 = (position_t){0, 0, 0}; - ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; - } - // handle cellSpecificKoffset_r17 if (ntn_Config_r17->cellSpecificKoffset_r17) ntn_ta->cell_specific_k_offset = *ntn_Config_r17->cellSpecificKoffset_r17; @@ -389,11 +368,9 @@ static void configure_ntn_ta(ntn_timing_advance_componets_t *ntn_ta, const NR_NT ntn_ta->N_common_ta_drift_variant = 0; } - ntn_ta->ntn_params_changed = true; - LOG_I(NR_MAC, - "SIB19 Rxd. Epoch HFN: %d, Epoch SFN: %d, Epoch Subframe: %d, k_offset: %ldms, " - "N_Common_Ta: %fms, drift: %fµs/s, variant %fµs/s²\n", + "NTN Config Rxd. Epoch HFN: %d, Epoch SFN: %d, Epoch Subframe: %d, k_offset: %ldms, " + "N_Common_Ta: %fms, drift: %fµs/s, variant: %fµs/s²\n", ntn_ta->epoch_hfn, ntn_ta->epoch_sfn, ntn_ta->epoch_subframe, @@ -401,6 +378,31 @@ static void configure_ntn_ta(ntn_timing_advance_componets_t *ntn_ta, const NR_NT ntn_ta->N_common_ta_adj, ntn_ta->N_common_ta_drift, ntn_ta->N_common_ta_drift_variant); + + // handle ephemerisInfo_r17 + const NR_EphemerisInfo_r17_t *ephemeris_info = ntn_Config_r17->ephemerisInfo_r17; + if (ephemeris_info) { + if (ephemeris_info->present == NR_EphemerisInfo_r17_PR_positionVelocity_r17) { + const NR_PositionVelocity_r17_t *position_velocity = ephemeris_info->choice.positionVelocity_r17; + AssertFatal(position_velocity, "position_velocity should not be NULL here\n"); + prepare_ue_sat_ta(position_velocity, ntn_ta); + } else { + LOG_W(NR_MAC, "NR UE currently supports only ephemerisInfo_r17 of type positionVelocity_r17\n"); + ntn_ta->omega = 0; + ntn_ta->pos_sat_0 = (position_t){0, 0, 0}; + ntn_ta->pos_sat_90 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_0 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; + } + } else { // Need R - Release if not present + ntn_ta->omega = 0; + ntn_ta->pos_sat_0 = (position_t){0, 0, 0}; + ntn_ta->pos_sat_90 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_0 = (position_t){0, 0, 0}; + ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; + } + + ntn_ta->ntn_params_changed = true; } static void config_common_ue(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommon_t *scc, int cc_idP, int hfn, int frame) From 834a6fc1d91bc983982906d10a3b3dce5ce42a3e Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Wed, 24 Sep 2025 14:27:45 +0200 Subject: [PATCH 13/43] NR UE: improve physical layer for NTN (esp. LEO) reestablishment and HO --- executables/nr-ue.c | 99 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index f3769096f8..d3595087e8 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -497,6 +497,8 @@ static uint64_t get_carrier_frequency(const int N_RB, const int mu, const uint32 return carrier_freq; } +static void apply_ntn_timing_advance_and_doppler(PHY_VARS_NR_UE *UE, const NR_DL_FRAME_PARMS *fp, int abs_subframe_tx); + static int handle_sync_req_from_mac(PHY_VARS_NR_UE *UE) { NR_DL_FRAME_PARMS *fp = &UE->frame_parms; @@ -521,6 +523,12 @@ static int handle_sync_req_from_mac(PHY_VARS_NR_UE *UE) init_symbol_rotation(fp); } + // Apply Doppler based on NTN-Config for target cell + if (UE->target_Nid_cell != fp->Nid_cell) + apply_ntn_timing_advance_and_doppler(UE, fp, -1); + // Apply NTN DL Doppler as initial FO + UE->initial_fo = UE->dl_Doppler_shift; + /* Clearing UE harq while DL actors are active causes race condition. So we let the current execution to complete here.*/ for (int i = 0; i < get_nrUE_params()->num_dl_actors; i++) { @@ -727,14 +735,40 @@ static inline int get_readBlockSize(uint16_t slot, NR_DL_FRAME_PARMS *fp) { return rem_samples + next_slot_first_symbol; } +static void fix_ntn_epoch_hfn(PHY_VARS_NR_UE *UE, int hfn, int frame) +{ + const int epoch_frame = UE->ntn_config_message->ntn_config_params.epoch_sfn; + const int diff1 = (frame - epoch_frame + 1024) % 1024; + const int diff2 = (epoch_frame - frame + 1024) % 1024; + + int *epoch_hfn = &UE->ntn_config_message->ntn_config_params.epoch_hfn; + if (diff1 < diff2) { // epoch_frame in the past + LOG_I(PHY, "epoch is %d frames in the past\n", diff1); + if (epoch_frame <= frame) + *epoch_hfn = hfn; + else + *epoch_hfn = hfn - 1; + } else { // epoch_frame in the future + LOG_I(PHY, "epoch is %d frames in the future\n", diff2); + if (epoch_frame >= frame) + *epoch_hfn = hfn; + else + *epoch_hfn = hfn + 1; + } + LOG_I(PHY, "setting epoch_hfn = %d\n", *epoch_hfn); +} + #define SPEED_OF_LIGHT 299792458 + +// calculate TA and Doppler based on the NTN-Config, if abs_subframe_tx < 0 then apply only Doppler static void apply_ntn_timing_advance_and_doppler(PHY_VARS_NR_UE *UE, const NR_DL_FRAME_PARMS *fp, int abs_subframe_tx) { const fapi_nr_dl_ntn_config_command_pdu *ntn_config_params = &UE->ntn_config_message->ntn_config_params; // Handle terrestrial networks if (ntn_config_params->cell_specific_k_offset == 0) { - UE->timing_advance_ntn = 0; + if (abs_subframe_tx >= 0) + UE->timing_advance_ntn = 0; UE->dl_Doppler_shift = 0; UE->ul_Doppler_shift = 0; return; @@ -743,7 +777,7 @@ static void apply_ntn_timing_advance_and_doppler(PHY_VARS_NR_UE *UE, const NR_DL const int abs_subframe_epoch = ntn_config_params->epoch_subframe + ntn_config_params->epoch_sfn * 10 + ntn_config_params->epoch_hfn * 10240; - const int ms_since_epoch = abs_subframe_tx - abs_subframe_epoch; + const int ms_since_epoch = abs_subframe_tx < 0 ? 0 : abs_subframe_tx - abs_subframe_epoch; const position_t pos_sat_0 = ntn_config_params->pos_sat_0; const position_t vel_sat_0 = ntn_config_params->vel_sat_0; @@ -797,22 +831,26 @@ static void apply_ntn_timing_advance_and_doppler(PHY_VARS_NR_UE *UE, const NR_DL const double N_common_ta_drift = ntn_config_params->N_common_ta_drift; const double N_common_ta_drift_variant = ntn_config_params->N_common_ta_drift_variant; - UE->timing_advance_ntn = (N_UE_TA_adj + N_common_ta_adj - + N_common_ta_drift * ms_since_epoch / 1e6 - + N_common_ta_drift_variant * ((int64_t)ms_since_epoch * ms_since_epoch) / 1e9) - * fp->samples_per_subframe; + if (abs_subframe_tx >= 0) { + UE->timing_advance_ntn = (N_UE_TA_adj + N_common_ta_adj + N_common_ta_drift * ms_since_epoch / 1e6 + + N_common_ta_drift_variant * ((int64_t)ms_since_epoch * ms_since_epoch) / 1e9) + * fp->samples_per_subframe; + } UE->freq_offset -= dl_Doppler_shift - UE->dl_Doppler_shift; UE->dl_Doppler_shift = dl_Doppler_shift; UE->ul_Doppler_shift = ul_Doppler_shift; - LOG_D(PHY, - "satellite velocity towards UE = %f m/s, DL Doppler shift = %f kHz, UL Doppler shift = %f kHz\n", - vel_sat_ue, - dl_Doppler_shift / 1000, - ul_Doppler_shift / 1000); + if (abs_subframe_tx < 0) { + LOG_I(PHY, + "satellite velocity towards UE = %f m/s, DL Doppler shift = %f kHz, UL Doppler shift = %f kHz\n", + vel_sat_ue, + dl_Doppler_shift / 1000, + ul_Doppler_shift / 1000); + } } +// apply values based on the NTN-Config static void apply_ntn_config(PHY_VARS_NR_UE *UE, NR_DL_FRAME_PARMS *fp, int hfn_rx, @@ -885,9 +923,14 @@ void *UE_thread(void *arg) notifiedFIFO_t freeBlocks; initNotifiedFIFO_nothreadSafe(&freeBlocks); - double ntn_init_time_drift = get_nrUE_params()->ntn_init_time_drift; - int ntn_koffset = 0; + const double ntn_init_time_drift = get_nrUE_params()->ntn_init_time_drift; + if (get_nrUE_params()->time_sync_I) + // ntn_init_time_drift is in µs/s, max_pos_acc * time_sync_I is in samples/frame + UE->max_pos_acc = ntn_init_time_drift * 1e-6 * fp->samples_per_frame / get_nrUE_params()->time_sync_I; + else + UE->max_pos_acc = 0; + int ntn_koffset = 0; int duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX; int timing_advance = UE->timing_advance + UE->timing_advance_ntn; UE->N_TA_offset = determine_N_TA_offset(UE); @@ -983,10 +1026,6 @@ void *UE_thread(void *arg) syncMsg->UE = UE; memset(&syncMsg->proc, 0, sizeof(syncMsg->proc)); pushNotifiedFIFO(&UE->sync_actor.fifo, Msg); - if (ntn_koffset == 0) { // reset timing_advance for TN - timing_advance = 0; - UE->timing_advance = 0; - } trashed_frames = 0; syncRunning = true; continue; @@ -996,13 +1035,8 @@ void *UE_thread(void *arg) stream_status = STREAM_STATUS_SYNCING; syncInFrame(UE, &sync_timestamp, duration_rx_to_tx, intialSyncOffset); openair0_write_reorder_clear_context(&UE->rfdevice); - if (get_nrUE_params()->time_sync_I) - // ntn_init_time_drift is in µs/s, max_pos_acc * time_sync_I is in samples/frame - UE->max_pos_acc = ntn_init_time_drift * 1e-6 * fp->samples_per_frame / get_nrUE_params()->time_sync_I; - else - UE->max_pos_acc = 0; shiftForNextFrame = -(UE->init_sync_frame + trashed_frames + 2) * UE->max_pos_acc * get_nrUE_params()->time_sync_I; // compensate for the time drift that happened during initial sync - LOG_D(PHY, "max_pos_acc = %d, shiftForNextFrame = %d\n", UE->max_pos_acc, shiftForNextFrame); + LOG_I(PHY, "max_pos_acc = %d, shiftForNextFrame = %d\n", UE->max_pos_acc, shiftForNextFrame); // read in first symbol AssertFatal(fp->ofdm_symbol_size + fp->nb_prefix_samples0 == UE->rfdevice.trx_read_func(&UE->rfdevice, @@ -1020,6 +1054,18 @@ void *UE_thread(void *arg) // Set to the slot where the SL-SSB was decoded absolute_slot += UE->SL_UE_PHY_PARAMS.sync_params.slot_offset; } + // With the correct frame and slot numbers, we can now fix the UL timing + const int frame_rx = (absolute_slot / nb_slot_frame) % 1024; + const int hfn_rx = (absolute_slot / nb_slot_frame) / 1024; + fix_ntn_epoch_hfn(UE, hfn_rx, frame_rx); + if (UE->ntn_config_message->update) { + apply_ntn_config(UE, fp, hfn_rx, frame_rx, 0, &duration_rx_to_tx, &timing_advance, &ntn_koffset); + } else { + const int mu = fp->numerology_index; + const int abs_subframe_tx = 10240 * hfn_rx + 10 * frame_rx + (duration_rx_to_tx >> mu); + apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); + } + UE->timing_advance = 0; // We have resynchronized, maybe after RF loss so we need to purge any existing context memset(tx_wait_for_dlsch, 0, sizeof(tx_wait_for_dlsch)); for (int i = 0; i < NUM_PROCESS_SLOT_TX_BARRIERS; i++) { @@ -1134,6 +1180,11 @@ void *UE_thread(void *arg) writeBlockSize -= new_N_TA_offset - UE->N_TA_offset; UE->N_TA_offset = new_N_TA_offset; } + if (writeBlockSize < 0) { + timing_advance += writeBlockSize - 1; + LOG_I(PHY, "writeBlockSize is %d, setting it to 1 and changing timing_advance to %d\n", writeBlockSize, timing_advance); + writeBlockSize = 1; + } if (curMsg.proc.nr_slot_rx == 0) nr_ue_rrc_timer_trigger(UE->Mod_id, curMsg.proc.hfn_rx, curMsg.proc.frame_rx, curMsg.proc.gNB_id); @@ -1166,7 +1217,7 @@ void *UE_thread(void *arg) curMsgTx->absolute_deadline_us = absolute_deadline_us; int slot = curMsgTx->proc.nr_slot_tx; - int slot_and_frame = slot + curMsgTx->proc.frame_tx * UE->frame_parms.slots_per_frame; + int slot_and_frame = slot + curMsgTx->proc.frame_tx * nb_slot_frame; int next_tx_slot_and_frame = absolute_slot + duration_rx_to_tx + 1; int wait_for_prev_slot = stream_status == STREAM_STATUS_SYNCED ? 1 : 0; From b5a025931c5dbaff87aa59e160a4960ea0e07c55 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Wed, 24 Sep 2025 14:38:09 +0200 Subject: [PATCH 14/43] verify reestablishment in 5g_rfsimulator_ntn_leo CI test --- .../xml_files/container_5g_rfsim_ntn_leo.xml | 32 +++++++++++++++++++ .../docker-compose.yaml | 4 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml b/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml index cde84b00e7..f00e6b26f1 100644 --- a/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml +++ b/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml @@ -32,6 +32,11 @@ 000003 020001 020002 + 040001 + 020022 + 040002 + 020001 + 020002 100001 222222 @@ -94,6 +99,33 @@ 5 + + Custom_Command + Trigger Reestablishment + localhost + echo ci force_reestab | ncat 192.168.71.140 9090 | grep -E 'Reset RLC counters of UE RNTI [0-9a-f]{4} to trigger reestablishment' + yes + + + + Ping + Ping ext-dn from NR-UE + rfsim5g_ue + localhost + rfsim5g_ext_dn + localhost + -c 20 -i0.5 -w25 + 80 + + + + Custom_Command + Verify Reestablishment + localhost + echo ci get_reestab_count | ncat 192.168.71.140 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 1' + yes + + Undeploy_Object true diff --git a/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml index 3b5e7170ba..a3f8ec2a7e 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml @@ -93,7 +93,9 @@ services: - ALL environment: USE_ADDITIONAL_OPTIONS: --rfsim --log_config.global_log_options level,nocolor,time - ASAN_OPTIONS: detect_leaks=0 + --telnetsrv --telnetsrv.listenaddr 192.168.71.140 + --telnetsrv.shrmod ci + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 depends_on: - oai-ext-dn networks: From 3dcaacb63bb1f476a38e9366fc6e25c8a99777ce Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Fri, 26 Sep 2025 11:57:27 +0200 Subject: [PATCH 15/43] NR UE FAPI: provide ntn_config as part of phy_config.config_req instead of a dedicated scheduled_response dl_config PDU We need the ntn_config when reconfiguring the PHY layer for a new initial sync, e.g. for calculating the DL Doppler frequency. As a dedicated scheduled_response dl_config PDU it reaches the PHY layer too late, as part of the phy_config.config_req it reaches the PHY layer in time with the other configuration values. --- executables/nr-ue.c | 17 +++--- .../nfapi/public_inc/fapi_nr_ue_constants.h | 4 +- .../nfapi/public_inc/fapi_nr_ue_interface.h | 6 +- openair1/PHY/INIT/nr_init_ue.c | 2 - openair1/PHY/defs_nr_UE.h | 6 -- openair1/SCHED_NR_UE/fapi_nr_ue_l1.c | 28 ---------- openair2/LAYER2/NR_MAC_UE/config_ue.c | 14 +++-- openair2/LAYER2/NR_MAC_UE/mac_defs.h | 35 +----------- openair2/LAYER2/NR_MAC_UE/main_ue_nr.c | 2 +- openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c | 4 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 22 ++++---- openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c | 55 ++++--------------- 12 files changed, 50 insertions(+), 145 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d3595087e8..41b347772c 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -177,8 +177,7 @@ void init_nr_ue_vars(PHY_VARS_NR_UE *ue, uint8_t UE_id) ue->dci_thres = 0; ue->target_Nid_cell = -1; - ue->ntn_config_message = CALLOC(1, sizeof(*ue->ntn_config_message)); - ue->ntn_config_message->update = false; + ue->nrUE_config.ntn_config.params_changed = false; // initialize all signal buffers init_nr_ue_signal(ue, nb_connected_gNB); @@ -737,11 +736,11 @@ static inline int get_readBlockSize(uint16_t slot, NR_DL_FRAME_PARMS *fp) { static void fix_ntn_epoch_hfn(PHY_VARS_NR_UE *UE, int hfn, int frame) { - const int epoch_frame = UE->ntn_config_message->ntn_config_params.epoch_sfn; + const int epoch_frame = UE->nrUE_config.ntn_config.epoch_sfn; const int diff1 = (frame - epoch_frame + 1024) % 1024; const int diff2 = (epoch_frame - frame + 1024) % 1024; - int *epoch_hfn = &UE->ntn_config_message->ntn_config_params.epoch_hfn; + int *epoch_hfn = &UE->nrUE_config.ntn_config.epoch_hfn; if (diff1 < diff2) { // epoch_frame in the past LOG_I(PHY, "epoch is %d frames in the past\n", diff1); if (epoch_frame <= frame) @@ -763,7 +762,7 @@ static void fix_ntn_epoch_hfn(PHY_VARS_NR_UE *UE, int hfn, int frame) // calculate TA and Doppler based on the NTN-Config, if abs_subframe_tx < 0 then apply only Doppler static void apply_ntn_timing_advance_and_doppler(PHY_VARS_NR_UE *UE, const NR_DL_FRAME_PARMS *fp, int abs_subframe_tx) { - const fapi_nr_dl_ntn_config_command_pdu *ntn_config_params = &UE->ntn_config_message->ntn_config_params; + const fapi_nr_ntn_config_t *ntn_config_params = &UE->nrUE_config.ntn_config; // Handle terrestrial networks if (ntn_config_params->cell_specific_k_offset == 0) { @@ -860,10 +859,10 @@ static void apply_ntn_config(PHY_VARS_NR_UE *UE, int *timing_advance, int *ntn_koffset) { - if (UE->ntn_config_message->update) { - UE->ntn_config_message->update = false; + if (UE->nrUE_config.ntn_config.params_changed) { + UE->nrUE_config.ntn_config.params_changed = false; - const fapi_nr_dl_ntn_config_command_pdu *ntn_config_params = &UE->ntn_config_message->ntn_config_params; + const fapi_nr_ntn_config_t *ntn_config_params = &UE->nrUE_config.ntn_config; const int mu = fp->numerology_index; const int koffset = ntn_config_params->cell_specific_k_offset; @@ -1058,7 +1057,7 @@ void *UE_thread(void *arg) const int frame_rx = (absolute_slot / nb_slot_frame) % 1024; const int hfn_rx = (absolute_slot / nb_slot_frame) / 1024; fix_ntn_epoch_hfn(UE, hfn_rx, frame_rx); - if (UE->ntn_config_message->update) { + if (UE->nrUE_config.ntn_config.params_changed) { apply_ntn_config(UE, fp, hfn_rx, frame_rx, 0, &duration_rx_to_tx, &timing_advance, &ntn_koffset); } else { const int mu = fp->numerology_index; diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h index 57292ecee5..04bb77f5de 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h @@ -51,9 +51,7 @@ #define FAPI_NR_DL_CONFIG_TYPE_CSI_RS 0x06 #define FAPI_NR_DL_CONFIG_TYPE_CSI_IM 0x07 #define FAPI_NR_CONFIG_TA_COMMAND 0x08 -#define FAPI_NR_DL_NTN_CONFIG_PARAMS 0x09 -#define FAPI_NR_DL_CONFIG_TYPES 0x09 - +#define FAPI_NR_DL_CONFIG_TYPES 0x08 #define FAPI_NR_CCE_REG_MAPPING_TYPE_INTERLEAVED 0x01 #define FAPI_NR_CCE_REG_MAPPING_TYPE_NON_INTERLEAVED 0x02 diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index 49752be093..b1f9fdae6d 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -587,7 +587,9 @@ typedef struct { // cell scheduling offset expressed in terms of 15kHz SCS long cell_specific_k_offset; -} fapi_nr_dl_ntn_config_command_pdu; + + bool params_changed; +} fapi_nr_ntn_config_t; typedef struct { uint8_t pdu_type; @@ -597,7 +599,6 @@ typedef struct { fapi_nr_dl_config_csirs_pdu csirs_config_pdu; fapi_nr_dl_config_csiim_pdu csiim_config_pdu; fapi_nr_ta_command_pdu ta_command_pdu; - fapi_nr_dl_ntn_config_command_pdu ntn_config_command_pdu; }; } fapi_nr_dl_config_request_pdu_t; @@ -729,6 +730,7 @@ typedef struct { fapi_nr_ssb_table_t ssb_table; fapi_nr_tdd_table_t tdd_table; fapi_nr_prach_config_t prach_config; + fapi_nr_ntn_config_t ntn_config; } fapi_nr_config_request_t; diff --git a/openair1/PHY/INIT/nr_init_ue.c b/openair1/PHY/INIT/nr_init_ue.c index 7ab8a8863d..48cc73cf79 100644 --- a/openair1/PHY/INIT/nr_init_ue.c +++ b/openair1/PHY/INIT/nr_init_ue.c @@ -338,8 +338,6 @@ void term_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB) free_and_zero(ue->prs_vars[idx]); } - free_and_zero(ue->ntn_config_message); - sl_ue_free(ue); } diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 5e2e169c1a..a166dc13cd 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -329,11 +329,6 @@ typedef struct UE_NR_SCAN_INFO_s { int32_t freq_offset_Hz[3][10]; } UE_NR_SCAN_INFO_t; -typedef struct { - bool update; - fapi_nr_dl_ntn_config_command_pdu ntn_config_params; -} ntn_config_message_t; - /// Top-level PHY Data Structure for UE typedef struct PHY_VARS_NR_UE_s { openair0_config_t openair0_cfg[MAX_CARDS]; @@ -527,7 +522,6 @@ typedef struct PHY_VARS_NR_UE_s { Actor_t sync_actor; Actor_t *dl_actors; Actor_t *ul_actors; - ntn_config_message_t* ntn_config_message; pthread_t main_thread; pthread_t stat_thread; } PHY_VARS_NR_UE; diff --git a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c index 92a0b043e7..885ba1381a 100644 --- a/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c +++ b/openair1/SCHED_NR_UE/fapi_nr_ue_l1.c @@ -89,31 +89,6 @@ static void configure_dlsch(NR_UE_DLSCH_t *dlsch0, } } -static void configure_ntn_params(PHY_VARS_NR_UE *ue, fapi_nr_dl_ntn_config_command_pdu* ntn_params_message) -{ - if (!ue->ntn_config_message) { - ue->ntn_config_message = CALLOC(1, sizeof(*ue->ntn_config_message)); - } - - ue->ntn_config_message->ntn_config_params.epoch_hfn = ntn_params_message->epoch_hfn; - ue->ntn_config_message->ntn_config_params.epoch_sfn = ntn_params_message->epoch_sfn; - ue->ntn_config_message->ntn_config_params.epoch_subframe = ntn_params_message->epoch_subframe; - - ue->ntn_config_message->ntn_config_params.omega = ntn_params_message->omega; - ue->ntn_config_message->ntn_config_params.pos_sat_0 = ntn_params_message->pos_sat_0; - ue->ntn_config_message->ntn_config_params.pos_sat_90 = ntn_params_message->pos_sat_90; - ue->ntn_config_message->ntn_config_params.vel_sat_0 = ntn_params_message->vel_sat_0; - ue->ntn_config_message->ntn_config_params.vel_sat_90 = ntn_params_message->vel_sat_90; - - ue->ntn_config_message->ntn_config_params.N_common_ta_adj = ntn_params_message->N_common_ta_adj; - ue->ntn_config_message->ntn_config_params.N_common_ta_drift = ntn_params_message->N_common_ta_drift; - ue->ntn_config_message->ntn_config_params.N_common_ta_drift_variant = ntn_params_message->N_common_ta_drift_variant; - - ue->ntn_config_message->ntn_config_params.cell_specific_k_offset = ntn_params_message->cell_specific_k_offset; - - ue->ntn_config_message->update = true; -} - static void configure_ta_command(PHY_VARS_NR_UE *ue, fapi_nr_ta_command_pdu *ta_command_pdu) { /* Time Alignment procedure @@ -230,9 +205,6 @@ static void nr_ue_scheduled_response_dl(NR_UE_MAC_INST_t *mac, case FAPI_NR_CONFIG_TA_COMMAND: configure_ta_command(phy, &pdu->ta_command_pdu); break; - case FAPI_NR_DL_NTN_CONFIG_PARAMS: - configure_ntn_params(phy, &pdu->ntn_config_command_pdu); - break; default: LOG_W(PHY, "unhandled dl pdu type %d \n", pdu->pdu_type); } diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index ded7c2a754..2b43883e3d 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -263,7 +263,7 @@ static void config_common_ue_sa(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommo } // prepare data for orbit propagation based on SIB19 ephemeris data to be able to compute the round-trip-time between ue and sat -static void prepare_ue_sat_ta(const NR_PositionVelocity_r17_t *sat_pos, ntn_timing_advance_componets_t *ntn_ta) +static void prepare_ue_sat_ta(const NR_PositionVelocity_r17_t *sat_pos, fapi_nr_ntn_config_t *ntn_ta) { // get sat position coordinates const position_t pos_sat = {sat_pos->positionX_r17 * 1.3, sat_pos->positionY_r17 * 1.3, sat_pos->positionZ_r17 * 1.3}; @@ -327,7 +327,7 @@ static void prepare_ue_sat_ta(const NR_PositionVelocity_r17_t *sat_pos, ntn_timi } // populate ntn_ta structure from mac -static void configure_ntn_ta(ntn_timing_advance_componets_t *ntn_ta, const NR_NTN_Config_r17_t *ntn_Config_r17, int hfn, int frame) +static void configure_ntn_ta(fapi_nr_ntn_config_t *ntn_ta, const NR_NTN_Config_r17_t *ntn_Config_r17, int hfn, int frame) { if (!ntn_Config_r17) return; @@ -402,7 +402,7 @@ static void configure_ntn_ta(ntn_timing_advance_componets_t *ntn_ta, const NR_NT ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; } - ntn_ta->ntn_params_changed = true; + ntn_ta->params_changed = true; } static void config_common_ue(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommon_t *scc, int cc_idP, int hfn, int frame) @@ -579,7 +579,7 @@ static void config_common_ue(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommon_t // NTN Config if (scc->ext2) { UPDATE_IE(mac->sc_info.ntn_Config_r17, scc->ext2->ntn_Config_r17, NR_NTN_Config_r17_t); - configure_ntn_ta(&mac->ntn_ta, mac->sc_info.ntn_Config_r17, hfn, frame); + configure_ntn_ta(&mac->phy_config.config_req.ntn_config, mac->sc_info.ntn_Config_r17, hfn, frame); } else { asn1cFreeStruc(asn_DEF_NR_NTN_Config_r17, mac->sc_info.ntn_Config_r17); } @@ -1977,6 +1977,7 @@ void nr_rrc_mac_config_req_sib1(module_id_t module_id, int cc_idP, NR_SIB1_t *si mac->state = UE_PERFORMING_RA; mac->if_module->phy_config_request(&mac->phy_config); + mac->phy_config.config_req.ntn_config.params_changed = false; ret = pthread_mutex_unlock(&mac->if_mutex); AssertFatal(!ret, "mutex failed %d\n", ret); } @@ -1990,7 +1991,9 @@ void nr_rrc_mac_config_other_sib(module_id_t module_id, NR_SIB19_r17_t *sib19, i if (sib19) { // update ntn_Config_r17 with received values UPDATE_IE(mac->sc_info.ntn_Config_r17, sib19->ntn_Config_r17, NR_NTN_Config_r17_t); - configure_ntn_ta(&mac->ntn_ta, mac->sc_info.ntn_Config_r17, hfn, frame); + configure_ntn_ta(&mac->phy_config.config_req.ntn_config, mac->sc_info.ntn_Config_r17, hfn, frame); + mac->if_module->phy_config_request(&mac->phy_config); + mac->phy_config.config_req.ntn_config.params_changed = false; } if (mac->state == UE_RECEIVING_SIB && can_start_ra) mac->state = UE_PERFORMING_RA; @@ -2053,6 +2056,7 @@ static void handle_reconfiguration_with_sync(NR_UE_MAC_INST_t *mac, mac->synch_request.synch_req.target_Nid_cell = mac->physCellId; mac->if_module->synch_request(&mac->synch_request); mac->if_module->phy_config_request(&mac->phy_config); + mac->phy_config.config_req.ntn_config.params_changed = false; } static void configure_physicalcellgroup(NR_UE_MAC_INST_t *mac, diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index 42b616c188..65bf66505b 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -545,35 +545,6 @@ typedef struct { A_SEQUENCE_OF(si_schedinfo_config_t) si_SchedInfo_list; } si_schedInfo_t; -typedef struct ntn_timing_advance_components { - int epoch_hfn; - int epoch_sfn; - int epoch_subframe; - - // orbital angular velocity in rad/ms - double omega; - // satellite position vector at epoch time - position_t pos_sat_0; - // satellite position vector at 90° orbit - position_t pos_sat_90; - // satellite velocity vector at epoch time - position_t vel_sat_0; - // satellite velocity vector at 90° orbit - position_t vel_sat_90; - - // N_common_ta_adj represents common round-trip-time between gNB and SAT received in SIB19 (ms) - double N_common_ta_adj; - // drift rate of common ta in µs/s - double N_common_ta_drift; - // change rate of common ta drift in µs/s² - double N_common_ta_drift_variant; - - // cell scheduling offset expressed in terms of 15kHz SCS - long cell_specific_k_offset; - - bool ntn_params_changed; -} ntn_timing_advance_componets_t; - /*!\brief Top level UE MAC structure */ typedef struct NR_UE_MAC_INST_s { module_id_t ue_id; @@ -637,8 +608,6 @@ typedef struct NR_UE_MAC_INST_s { int p_Max; int p_Max_alt; - ntn_timing_advance_componets_t ntn_ta; - long pdsch_HARQ_ACK_Codebook; NR_Type0_PDCCH_CSS_config_t type0_PDCCH_CSS_config; @@ -679,12 +648,12 @@ typedef struct NR_UE_MAC_INST_s { notifiedFIFO_t input_nf; } NR_UE_MAC_INST_t; -static inline int GET_NTN_UE_K_OFFSET(const ntn_timing_advance_componets_t *ntn_ta, int scs) +static inline int GET_NTN_UE_K_OFFSET(const fapi_nr_ntn_config_t *ntn_ta, int scs) { return (int)ntn_ta->cell_specific_k_offset << scs; } -static inline long GET_DURATION_RX_TO_TX(const ntn_timing_advance_componets_t *ntn_ta, int scs) +static inline long GET_DURATION_RX_TO_TX(const fapi_nr_ntn_config_t *ntn_ta, int scs) { return NR_UE_CAPABILITY_SLOT_RX_TO_TX + (ntn_ta->cell_specific_k_offset << scs); } diff --git a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c index 4cf96b46bc..461d21f3aa 100644 --- a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c +++ b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c @@ -69,7 +69,7 @@ void nr_ue_init_mac(NR_UE_MAC_INST_t *mac) mac->p_Max = INT_MIN; mac->p_Max_alt = INT_MIN; mac->msg3_C_RNTI = false; - mac->ntn_ta.ntn_params_changed = false; + mac->phy_config.config_req.ntn_config.params_changed = false; initNotifiedFIFO(&mac->input_nf); reset_mac_inst(mac); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c index ecf8edb022..dfe16de822 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c @@ -813,7 +813,7 @@ static void setup_ra_response_window(NR_UE_MAC_INST_t *mac, LOG_E(NR_MAC, "RA-ResponseWindow need to be configured to a value lower than or equal to 10 ms\n"); } - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, mac->current_UL_BWP->scs); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); ra->response_window_setup_time = respwind_value + ntn_ue_koffset; } @@ -1027,7 +1027,7 @@ void nr_Msg3_transmitted(NR_UE_MAC_INST_t *mac, uint8_t CC_id, frame_t frameP, s { RA_config_t *ra = &mac->ra; NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = mac->current_UL_BWP->rach_ConfigCommon; - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, mac->current_UL_BWP->scs); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); const int slots_per_ms = mac->frame_structure.numb_slots_frame / 10; // start contention resolution timer diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index 86d7b9254f..241a56b180 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -563,7 +563,7 @@ static int nr_ue_process_dci_ul_00(NR_UE_MAC_INST_t *mac, frame_t frame_tx; int slot_tx; - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, mac->current_UL_BWP->scs); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); if (-1 == nr_ue_pusch_scheduler(mac, 0, frame, slot, &frame_tx, &slot_tx, tda_info.k2 + ntn_ue_koffset)) { LOG_E(MAC, "Cannot schedule PUSCH\n"); return -1; @@ -661,7 +661,7 @@ static int nr_ue_process_dci_ul_01(NR_UE_MAC_INST_t *mac, tda_info.k2 = csi_K2; } - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, mac->current_UL_BWP->scs); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); if (-1 == nr_ue_pusch_scheduler(mac, 0, frame, slot, &frame_tx, &slot_tx, tda_info.k2 + ntn_ue_koffset)) { LOG_E(MAC, "Cannot schedule PUSCH\n"); return -1; @@ -998,7 +998,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac, /* PDSCH_TO_HARQ_FEEDBACK_TIME_IND */ // according to TS 38.213 9.2.3 - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing); uint16_t feedback_ti = 0; if (rnti_type == TYPE_RA_RNTI_) { @@ -1006,17 +1006,17 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac, // that can process the MSG3. Also assume that RAR is sent in the same slot as DCI (k0 == 0) // This is not perfect as the MSG3 might end up being scheduled later, so we could be // halting the UL scheduler for a longer time than necessary. - feedback_ti = max(1 + GET_DURATION_RX_TO_TX(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing), + feedback_ti = max(1 + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing), get_delta_for_k2(mac->current_UL_BWP->scs) + get_j_for_k2(mac->current_UL_BWP->scs)); } if (rnti_type != TYPE_RA_RNTI_ && rnti_type != TYPE_SI_RNTI_) { if (!get_FeedbackDisabled(mac->sc_info.downlinkHARQ_FeedbackDisabled_r17, dci->harq_pid.val)) { feedback_ti = 1 + dci->pdsch_to_harq_feedback_timing_indicator.val + ntn_ue_koffset; - AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing), + AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing), "PDSCH to HARQ feedback time (%d) needs to be higher than DURATION_RX_TO_TX (%ld).\n", feedback_ti, - GET_DURATION_RX_TO_TX(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing)); + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing)); // set the harq status at MAC for feedback const int tpc[] = {-1, 0, 1, 3}; set_harq_status(mac, @@ -1312,16 +1312,16 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac, /* PDSCH_TO_HARQ_FEEDBACK_TIME_IND */ // according to TS 38.213 Table 9.2.3-1 - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing); uint16_t feedback_ti = 0; if (!get_FeedbackDisabled(mac->sc_info.downlinkHARQ_FeedbackDisabled_r17, dci->harq_pid.val)) { feedback_ti = pucch_Config->dl_DataToUL_ACK->list.array[dci->pdsch_to_harq_feedback_timing_indicator.val][0] + ntn_ue_koffset; - AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing), + AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing), "PDSCH to HARQ feedback time (%d) needs to be higher than DURATION_RX_TO_TX (%ld). Min feedback time set in config " "file (min_rxtxtime).\n", feedback_ti, - GET_DURATION_RX_TO_TX(&mac->ntn_ta, dlsch_pdu->SubcarrierSpacing)); + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing)); // set the harq status at MAC for feedback const int tpc[] = {-1, 0, 1, 3}; @@ -3620,7 +3620,7 @@ static void set_time_alignment(NR_UE_MAC_INST_t *mac, int ta, ta_type_t type, in NR_UL_TIME_ALIGNMENT_t *ul_time_alignment = &mac->ul_time_alignment; ul_time_alignment->ta_command = ta; ul_time_alignment->ta_apply = type; - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, mac->current_UL_BWP->scs); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); const int n_slots_frame = mac->frame_structure.numb_slots_frame; ul_time_alignment->frame = (frame + (slot + ntn_ue_koffset) / n_slots_frame) % MAX_FRAME_NUMBER; ul_time_alignment->slot = (slot + ntn_ue_koffset) % n_slots_frame; @@ -4095,7 +4095,7 @@ static void handle_rar_reception(NR_UE_MAC_INST_t *mac, NR_MAC_RAR *rar, frame_t } frame_t frame_tx = 0; int slot_tx = 0; - const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->ntn_ta, mac->current_UL_BWP->scs); + const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, mac->current_UL_BWP->scs); int ret = nr_ue_pusch_scheduler(mac, 1, frame, slot, &frame_tx, &slot_tx, tda_info.k2 + ntn_ue_koffset); // TA command diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c index f7a763f41a..c21a0ce3bd 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c @@ -59,7 +59,6 @@ static void nr_ue_prach_scheduler(NR_UE_MAC_INST_t *mac, frame_t frameP, slot_t slotP); static void schedule_ta_command(fapi_nr_dl_config_request_t *dl_config, NR_UE_MAC_INST_t *mac); -static void schedule_ntn_config_command(fapi_nr_dl_config_request_t *dl_config, NR_UE_MAC_INST_t *mac); static void nr_ue_fill_phr(NR_UE_MAC_INST_t *mac, NR_SINGLE_ENTRY_PHR_MAC_CE *phr, @@ -1056,10 +1055,10 @@ void nr_ue_aperiodic_srs_scheduling(NR_UE_MAC_INST_t *mac, long resource_trigger } int scs = mac->current_UL_BWP->scs; - AssertFatal(slot_offset > GET_DURATION_RX_TO_TX(&mac->ntn_ta, scs), + AssertFatal(slot_offset > GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, scs), "Slot offset between DCI and aperiodic SRS (%d) needs to be higher than DURATION_RX_TO_TX (%ld)\n", slot_offset, - GET_DURATION_RX_TO_TX(&mac->ntn_ta, scs)); + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, scs)); int n_slots_frame = mac->frame_structure.numb_slots_frame; int sched_slot = (slot + slot_offset) % n_slots_frame; if (!is_ul_slot(sched_slot, &mac->frame_structure)) { @@ -1157,11 +1156,6 @@ void nr_ue_dl_scheduler(NR_UE_MAC_INST_t *mac, nr_downlink_indication_t *dl_info if (mac->ul_time_alignment.ta_apply != no_ta) schedule_ta_command(dl_config, mac); - - if (mac->ntn_ta.ntn_params_changed) { - mac->ntn_ta.ntn_params_changed = false; - schedule_ntn_config_command(dl_config, mac); - } nr_scheduled_response_t scheduled_response = {.dl_config = dl_config, .module_id = mac->ue_id, @@ -1570,24 +1564,24 @@ int nr_ue_pusch_scheduler(const NR_UE_MAC_INST_t *mac, delta = get_delta_for_k2(mu); - AssertFatal((k2 + delta) > GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu), + AssertFatal((k2 + delta) > GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu), "Slot offset (%ld) for Msg3 needs to be higher than DURATION_RX_TO_TX (%ld). Please set min_rxtxtime at least to " "%ld in gNB config file or gNBs.[0].min_rxtxtime=%ld via command line.\n", k2, - GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu), - GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu), - GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu)); + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu), + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu), + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu)); *slot_tx = (current_slot + k2 + delta) % slots_per_frame; *frame_tx = (current_frame + (current_slot + k2 + delta) / slots_per_frame) % MAX_FRAME_NUMBER; } else { - AssertFatal(k2 >= GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu), + AssertFatal(k2 >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu), "Slot offset K2 (%ld) needs to be higher than DURATION_RX_TO_TX (%ld). Please set min_rxtxtime at least to %ld in " "gNB config file or gNBs.[0].min_rxtxtime=%ld via command line.\n", k2, - GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu), - GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu), - GET_DURATION_RX_TO_TX(&mac->ntn_ta, mu)); + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu), + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu), + GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mu)); if (k2 < 0) { // This can happen when a false DCI is received LOG_W(PHY, "%d.%d. Received k2 %ld\n", current_frame, current_slot, k2); @@ -2210,9 +2204,8 @@ static void nr_ue_prach_scheduler(NR_UE_MAC_INST_t *mac, frame_t frameP, slot_t next_slot = temp_slot; add_slots++; } - nr_timer_setup(&ra->response_window_timer, - ra->response_window_setup_time + add_slots + GET_DURATION_RX_TO_TX(&mac->ntn_ta, mac->current_DL_BWP->scs), - 1); + add_slots += GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, mac->current_DL_BWP->scs); + nr_timer_setup(&ra->response_window_timer, ra->response_window_setup_time + add_slots, 1); nr_timer_start(&ra->response_window_timer); } else if (ra->ra_type == RA_2_STEP) { NR_MsgA_PUSCH_Resource_r16_t *msgA_PUSCH_Resource = @@ -2844,30 +2837,6 @@ static uint8_t nr_ue_get_sdu(NR_UE_MAC_INST_t *mac, return mac_ce_info.num_sdus > 0; // success if we got at least one sdu } -static void schedule_ntn_config_command(fapi_nr_dl_config_request_t *dl_config, NR_UE_MAC_INST_t *mac) -{ - fapi_nr_dl_ntn_config_command_pdu *ntn_config_command_pdu = &dl_config->dl_config_list[dl_config->number_pdus].ntn_config_command_pdu; - - ntn_config_command_pdu->epoch_hfn = mac->ntn_ta.epoch_hfn; - ntn_config_command_pdu->epoch_sfn = mac->ntn_ta.epoch_sfn; - ntn_config_command_pdu->epoch_subframe = mac->ntn_ta.epoch_subframe; - - ntn_config_command_pdu->omega = mac->ntn_ta.omega; - ntn_config_command_pdu->pos_sat_0 = mac->ntn_ta.pos_sat_0; - ntn_config_command_pdu->pos_sat_90 = mac->ntn_ta.pos_sat_90; - ntn_config_command_pdu->vel_sat_0 = mac->ntn_ta.vel_sat_0; - ntn_config_command_pdu->vel_sat_90 = mac->ntn_ta.vel_sat_90; - - ntn_config_command_pdu->N_common_ta_adj = mac->ntn_ta.N_common_ta_adj; - ntn_config_command_pdu->N_common_ta_drift = mac->ntn_ta.N_common_ta_drift; - ntn_config_command_pdu->N_common_ta_drift_variant = mac->ntn_ta.N_common_ta_drift_variant; - - ntn_config_command_pdu->cell_specific_k_offset = mac->ntn_ta.cell_specific_k_offset; - - dl_config->dl_config_list[dl_config->number_pdus].pdu_type = FAPI_NR_DL_NTN_CONFIG_PARAMS; - dl_config->number_pdus += 1; -} - static void schedule_ta_command(fapi_nr_dl_config_request_t *dl_config, NR_UE_MAC_INST_t *mac) { NR_UL_TIME_ALIGNMENT_t *ul_time_alignment = &mac->ul_time_alignment; From 1f8fe224a0e60423a1b58f1968c33b58bfa11ff1 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Fri, 26 Sep 2025 12:07:01 +0200 Subject: [PATCH 16/43] NR UE: correctly apply Doppler and TA for NTN target cell Make sure to apply Doppler and TA not directly when receiving the target cell ntn_config, but apply Doppler just before the initial sync, and the TA after the initial sync when we have tha actuel cell frame and slot numbers. --- executables/nr-ue.c | 79 ++++++++++++------- .../nfapi/public_inc/fapi_nr_ue_interface.h | 1 + openair2/LAYER2/NR_MAC_UE/config_ue.c | 11 ++- 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 41b347772c..231db3b91d 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -523,7 +523,7 @@ static int handle_sync_req_from_mac(PHY_VARS_NR_UE *UE) } // Apply Doppler based on NTN-Config for target cell - if (UE->target_Nid_cell != fp->Nid_cell) + if (UE->nrUE_config.ntn_config.is_targetcell) apply_ntn_timing_advance_and_doppler(UE, fp, -1); // Apply NTN DL Doppler as initial FO UE->initial_fo = UE->dl_Doppler_shift; @@ -857,37 +857,46 @@ static void apply_ntn_config(PHY_VARS_NR_UE *UE, int slot_rx, int *duration_rx_to_tx, int *timing_advance, - int *ntn_koffset) + int *ntn_koffset, + bool *ntn_targetcell) { - if (UE->nrUE_config.ntn_config.params_changed) { - UE->nrUE_config.ntn_config.params_changed = false; + // return if there is no pending update + if (!UE->nrUE_config.ntn_config.params_changed) + return; - const fapi_nr_ntn_config_t *ntn_config_params = &UE->nrUE_config.ntn_config; + *ntn_targetcell = UE->nrUE_config.ntn_config.is_targetcell; - const int mu = fp->numerology_index; - const int koffset = ntn_config_params->cell_specific_k_offset; + // skip update if it is for the target cell + if (UE->nrUE_config.ntn_config.is_targetcell) + return; - *duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX + (koffset << mu); - if (koffset > *ntn_koffset) - *timing_advance += get_samples_slot_duration(fp, slot_rx, (koffset - *ntn_koffset) << mu); - else if (koffset < *ntn_koffset) - *timing_advance -= get_samples_slot_duration(fp, slot_rx, (*ntn_koffset - koffset) << mu); - *ntn_koffset = koffset; + UE->nrUE_config.ntn_config.params_changed = false; - const int abs_subframe_tx = 10240 * hfn_rx + 10 * frame_rx + ((slot_rx + *duration_rx_to_tx) >> mu); - apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); + const fapi_nr_ntn_config_t *ntn_config_params = &UE->nrUE_config.ntn_config; - LOG_I(PHY, - "k_offset: %dms, N_Common_Ta: %fms, drift: %fµs/s, variant: %fµs/s², " - "timing_advance_ntn: %d samples, DL Doppler shift: %fkHz, UL Doppler shift: %fkHz\n", - koffset << mu, - ntn_config_params->N_common_ta_adj, - ntn_config_params->N_common_ta_drift, - ntn_config_params->N_common_ta_drift_variant, - UE->timing_advance_ntn, - UE->dl_Doppler_shift / 1000, - UE->ul_Doppler_shift / 1000); - } + const int mu = fp->numerology_index; + const int koffset = ntn_config_params->cell_specific_k_offset; + + *duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX + (koffset << mu); + if (koffset > *ntn_koffset) + *timing_advance += get_samples_slot_duration(fp, slot_rx, (koffset - *ntn_koffset) << mu); + else if (koffset < *ntn_koffset) + *timing_advance -= get_samples_slot_duration(fp, slot_rx, (*ntn_koffset - koffset) << mu); + *ntn_koffset = koffset; + + const int abs_subframe_tx = 10240 * hfn_rx + 10 * frame_rx + ((slot_rx + *duration_rx_to_tx) >> mu); + apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); + + LOG_I(PHY, + "k_offset: %dms, N_Common_Ta: %fms, drift: %fµs/s, variant: %fµs/s², " + "timing_advance_ntn: %d samples, DL Doppler shift: %fkHz, UL Doppler shift: %fkHz\n", + koffset << mu, + ntn_config_params->N_common_ta_adj, + ntn_config_params->N_common_ta_drift, + ntn_config_params->N_common_ta_drift_variant, + UE->timing_advance_ntn, + UE->dl_Doppler_shift / 1000, + UE->ul_Doppler_shift / 1000); } void *UE_thread(void *arg) @@ -929,6 +938,7 @@ void *UE_thread(void *arg) else UE->max_pos_acc = 0; + bool ntn_targetcell = false; int ntn_koffset = 0; int duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX; int timing_advance = UE->timing_advance + UE->timing_advance_ntn; @@ -1057,12 +1067,14 @@ void *UE_thread(void *arg) const int frame_rx = (absolute_slot / nb_slot_frame) % 1024; const int hfn_rx = (absolute_slot / nb_slot_frame) / 1024; fix_ntn_epoch_hfn(UE, hfn_rx, frame_rx); + UE->nrUE_config.ntn_config.is_targetcell = false; if (UE->nrUE_config.ntn_config.params_changed) { - apply_ntn_config(UE, fp, hfn_rx, frame_rx, 0, &duration_rx_to_tx, &timing_advance, &ntn_koffset); + apply_ntn_config(UE, fp, hfn_rx, frame_rx, 0, &duration_rx_to_tx, &timing_advance, &ntn_koffset, &ntn_targetcell); } else { const int mu = fp->numerology_index; const int abs_subframe_tx = 10240 * hfn_rx + 10 * frame_rx + (duration_rx_to_tx >> mu); apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); + ntn_targetcell = false; } UE->timing_advance = 0; // We have resynchronized, maybe after RF loss so we need to purge any existing context @@ -1124,7 +1136,8 @@ void *UE_thread(void *arg) } // Calculate new TA based on SIB19 information for each subframe in NTN mode, if "autonomous_ta" is not enabled - if (ntn_koffset && !get_nrUE_params()->autonomous_ta && (absolute_slot + duration_rx_to_tx) % fp->slots_per_subframe == 0) { + if (ntn_koffset && !ntn_targetcell && !get_nrUE_params()->autonomous_ta + && (absolute_slot + duration_rx_to_tx) % fp->slots_per_subframe == 0) { const int abs_subframe_tx = (absolute_slot + duration_rx_to_tx) / fp->slots_per_subframe; apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); } @@ -1202,7 +1215,15 @@ void *UE_thread(void *arg) } // apply new NTN timing information - apply_ntn_config(UE, fp, curMsg.proc.hfn_rx, curMsg.proc.frame_rx, curMsg.proc.nr_slot_rx, &duration_rx_to_tx, &timing_advance, &ntn_koffset); + apply_ntn_config(UE, + fp, + curMsg.proc.hfn_rx, + curMsg.proc.frame_rx, + curMsg.proc.nr_slot_rx, + &duration_rx_to_tx, + &timing_advance, + &ntn_koffset, + &ntn_targetcell); // Start TX slot processing here. It runs in parallel with RX slot processing // in current code, DURATION_RX_TO_TX constant is the limit to get UL data to encode from a RX slot diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index b1f9fdae6d..6b4648f572 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -588,6 +588,7 @@ typedef struct { // cell scheduling offset expressed in terms of 15kHz SCS long cell_specific_k_offset; + bool is_targetcell; bool params_changed; } fapi_nr_ntn_config_t; diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index 2b43883e3d..4077e793ab 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -327,7 +327,11 @@ static void prepare_ue_sat_ta(const NR_PositionVelocity_r17_t *sat_pos, fapi_nr_ } // populate ntn_ta structure from mac -static void configure_ntn_ta(fapi_nr_ntn_config_t *ntn_ta, const NR_NTN_Config_r17_t *ntn_Config_r17, int hfn, int frame) +static void configure_ntn_ta(fapi_nr_ntn_config_t *ntn_ta, + const NR_NTN_Config_r17_t *ntn_Config_r17, + int hfn, + int frame, + bool is_targetcell) { if (!ntn_Config_r17) return; @@ -402,6 +406,7 @@ static void configure_ntn_ta(fapi_nr_ntn_config_t *ntn_ta, const NR_NTN_Config_r ntn_ta->vel_sat_90 = (position_t){0, 0, 0}; } + ntn_ta->is_targetcell = is_targetcell; ntn_ta->params_changed = true; } @@ -579,7 +584,7 @@ static void config_common_ue(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommon_t // NTN Config if (scc->ext2) { UPDATE_IE(mac->sc_info.ntn_Config_r17, scc->ext2->ntn_Config_r17, NR_NTN_Config_r17_t); - configure_ntn_ta(&mac->phy_config.config_req.ntn_config, mac->sc_info.ntn_Config_r17, hfn, frame); + configure_ntn_ta(&mac->phy_config.config_req.ntn_config, mac->sc_info.ntn_Config_r17, hfn, frame, true); } else { asn1cFreeStruc(asn_DEF_NR_NTN_Config_r17, mac->sc_info.ntn_Config_r17); } @@ -1991,7 +1996,7 @@ void nr_rrc_mac_config_other_sib(module_id_t module_id, NR_SIB19_r17_t *sib19, i if (sib19) { // update ntn_Config_r17 with received values UPDATE_IE(mac->sc_info.ntn_Config_r17, sib19->ntn_Config_r17, NR_NTN_Config_r17_t); - configure_ntn_ta(&mac->phy_config.config_req.ntn_config, mac->sc_info.ntn_Config_r17, hfn, frame); + configure_ntn_ta(&mac->phy_config.config_req.ntn_config, mac->sc_info.ntn_Config_r17, hfn, frame, false); mac->if_module->phy_config_request(&mac->phy_config); mac->phy_config.config_req.ntn_config.params_changed = false; } From 30afbc41a67adaccbb0f801771254ad78e147d15 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Mon, 27 Oct 2025 17:20:11 +0100 Subject: [PATCH 17/43] update FEATURE_SET.md --- doc/FEATURE_SET.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index a29176ddd7..34a3f17987 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -355,7 +355,8 @@ These modes of operation are supported: - Different TA adjustemt algorithms between SIB19 receptions: - Autonomous TA adjustemt based on DL time tracking - Standard compliant epoch time based TA adjustment including orbital propagation - - UL Doppler pre-compensation based on continuous DL FO estimation + - DL Doppler compensation based on ntn-Config-r17 information + - UL Doppler pre-compensation based on ntn-Config-r17 information and residual DL FO estimation * Highly efficient 3GPP compliant LDPC encoder and decoder (BG1 and BG2 are supported) * Highly efficient 3GPP compliant polar encoder and decoder * Encoder and decoder for short block From f6fd7b61ece582ccc38b17d34da74aaaf20ea5b8 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Mon, 24 Nov 2025 16:55:09 +0100 Subject: [PATCH 18/43] NR UE: improve NTN reestablishment by continuing with previous HFN --- executables/nr-ue.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 231db3b91d..3b0e1c5ca7 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -736,6 +736,10 @@ static inline int get_readBlockSize(uint16_t slot, NR_DL_FRAME_PARMS *fp) { static void fix_ntn_epoch_hfn(PHY_VARS_NR_UE *UE, int hfn, int frame) { + if (!UE->nrUE_config.ntn_config.is_targetcell) + return; + UE->nrUE_config.ntn_config.is_targetcell = false; + const int epoch_frame = UE->nrUE_config.ntn_config.epoch_sfn; const int diff1 = (frame - epoch_frame + 1024) % 1024; const int diff2 = (epoch_frame - frame + 1024) % 1024; @@ -1057,22 +1061,31 @@ void *UE_thread(void *arg) // we have the decoded frame index in the return of the synch process // and we shifted above to the first slot of next frame decoded_frame_rx = (decoded_frame_rx + 1) % MAX_FRAME_NUMBER; + const int prev_frame_rx = (absolute_slot / nb_slot_frame) % MAX_FRAME_NUMBER; + const int prev_hfn_rx = (absolute_slot / nb_slot_frame) / MAX_FRAME_NUMBER; + int decoded_hfn_rx = prev_hfn_rx; + if (decoded_frame_rx <= prev_frame_rx) + decoded_hfn_rx++; // we do ++ first in the regular processing, so it will be begin of frame; - absolute_slot = decoded_frame_rx * nb_slot_frame - 1; + absolute_slot = (decoded_hfn_rx * MAX_FRAME_NUMBER + decoded_frame_rx) * nb_slot_frame - 1; if (UE->sl_mode == 2) { // Set to the slot where the SL-SSB was decoded absolute_slot += UE->SL_UE_PHY_PARAMS.sync_params.slot_offset; } // With the correct frame and slot numbers, we can now fix the UL timing - const int frame_rx = (absolute_slot / nb_slot_frame) % 1024; - const int hfn_rx = (absolute_slot / nb_slot_frame) / 1024; - fix_ntn_epoch_hfn(UE, hfn_rx, frame_rx); - UE->nrUE_config.ntn_config.is_targetcell = false; + fix_ntn_epoch_hfn(UE, decoded_hfn_rx, decoded_frame_rx); if (UE->nrUE_config.ntn_config.params_changed) { - apply_ntn_config(UE, fp, hfn_rx, frame_rx, 0, &duration_rx_to_tx, &timing_advance, &ntn_koffset, &ntn_targetcell); + apply_ntn_config(UE, + fp, + decoded_hfn_rx, + decoded_frame_rx, + 0, + &duration_rx_to_tx, + &timing_advance, + &ntn_koffset, + &ntn_targetcell); } else { - const int mu = fp->numerology_index; - const int abs_subframe_tx = 10240 * hfn_rx + 10 * frame_rx + (duration_rx_to_tx >> mu); + const int abs_subframe_tx = (absolute_slot + 1 + duration_rx_to_tx) / fp->slots_per_subframe; apply_ntn_timing_advance_and_doppler(UE, fp, abs_subframe_tx); ntn_targetcell = false; } From aa3a20984239b6d6f32d93a086254b8c4d886dd7 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Mon, 24 Nov 2025 17:37:43 +0100 Subject: [PATCH 19/43] NR UE: add option "--cont-fo-comp 3" to not consider measured residual DL FO for UL FO pre-compensation --- doc/RUNMODEM.md | 2 +- executables/nr-ue.c | 2 ++ executables/nr-uesoftmodem.h | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 37b51b96fa..3345dc114d 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -135,7 +135,7 @@ Here are some useful command line options for the NR UE: |--------------------------|---------------------------------------------------------------------------------------------------------------| | `--ue-scan-carrier` | Scan for cells in current bandwidth. This option can be used if the SSB position of the gNB is unknown. If multiple cells are detected, the UE will try to connect to the first cell. By default, this option is disabled and the UE attempts to only decode SSB given by `--ssb`. | | `--ue-fo-compensation` | Enables the initial frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | -| `--cont-fo-comp` | Enables the continuous frequency offset (FO) estimation and compensation. Parameter value `1` specifies that the main FO contribution comes from the local oscillator's (LO) accuracy. Parameter value `2` specifies that the main FO contribution comes from Doppler shift. | +| `--cont-fo-comp` | Enables the continuous frequency offset (FO) estimation and compensation. Parameter value `1` specifies that the main FO contribution comes from the local oscillator's (LO) accuracy. Parameter value `2` specifies that the main FO contribution comes from Doppler shift. Parameter value `3` specifies that no measured residual DL FO is considered for UL FO pre-compensation. | | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--freq-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the continuous frequency offset compensation. Default value 0.01. | | `--freq-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the continuous frequency offset compensation. Default value 0.001. | diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 3b0e1c5ca7..676c318333 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -398,6 +398,8 @@ static void RU_write(nr_rxtx_thread_data_t *rxtxD, bool sl_tx_action, c16_t **tx double ul_freq_offset = -UE->freq_offset * ((double)fp->ul_CarrierFreq / (double)fp->dl_CarrierFreq); if (get_nrUE_params()->cont_fo_comp == 2) // different from LO frequency error compensation, Doppler UL pre-compensation has to be negative ul_freq_offset = -ul_freq_offset; + else if (get_nrUE_params()->cont_fo_comp == 3) // do not consider residual DL FO for UL pre-compensation at all + ul_freq_offset = 0; for (int i = 0; i < fp->nb_antennas_tx; i++) nr_fo_compensation(UE->ul_Doppler_shift + ul_freq_offset, fp->samples_per_subframe, diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index df13e2e02e..f14594b0dd 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -21,7 +21,7 @@ extern uint16_t ue_id_g; #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" #define CONFIG_HLP_FREQ_SYNC_P "coefficient for Proportional part of continuous frequency offset compensation PI controller\n" #define CONFIG_HLP_FREQ_SYNC_I "coefficient for Integrating part of continuous frequency offset compensation PI controller\n" -#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset (FO) estimation and compensation and specify main FO source (1 = local oscillator, 2 = Doppler shift)\n" +#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset (FO) estimation and (pre-)compensation and specify main FO source (1 = local oscillator, 2 = Doppler shift, 3 = Don't consider residual DL FO in UL FO pre-compensation)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" #define CONFIG_HLP_NUM_UL_ACTORS "Number of UL actors to use. Set to 0 to disable UL actor framework and do processing inline\n" #define CONFIG_HLP_NUM_DL_ACTORS "Number of DL actors to use. Set to 0 to disable DL actor framework and do processing inline\n" From 2454a2ea1e474e2e010659ae6be5ae2481d00d1d Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 11 Sep 2025 16:38:19 +0200 Subject: [PATCH 20/43] automatic selection of SIB1 TDA --- .../gnb-vnf.sa.band66.u0.25prb.nfapi.conf | 2 - .../gnb.sa.band261.u3.32prb.rfsim.conf | 2 - .../conf_files/gnb.sa.band78.24prb.rfsim.conf | 2 - ...sa.band78.273prb.fhi72.4x4-benetel550.conf | 1 - doc/MAC/mac-usage.md | 2 - doc/RUNMODEM.md | 1 - openair2/GNB_APP/gnb_config.c | 3 - openair2/GNB_APP/gnb_paramdef.h | 50 ++++---- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 119 ++++++++++-------- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1 - .../gnb-du.sa.band66.25prb.rfsim.pci0.conf | 1 - .../gnb-du.sa.band66.25prb.rfsim.pci1.conf | 1 - ...sa.band77.273prb.fhi72.4x4-benetel650.conf | 1 - ...73prb.fhi72.8x8-benetel650_650-mplane.conf | 1 - ...and77.273prb.fhi72.8x8-benetel650_650.conf | 1 - .../CONF/gnb-vnf.sa.band78.106prb.nfapi.conf | 1 - .../gnb.sa.band254.ntn.mu1.24prb.rfsim.conf | 1 - .../gnb.sa.band255.ntn.mu0.25prb.rfsim.conf | 1 - .../gnb.sa.band256.ntn.mu0.25prb.rfsim.conf | 1 - .../gnb.sa.band257.u3.32prb.usrpx410.conf | 1 - .../gnb.sa.band41.fr1.52PRB.usrpb210.conf | 1 - .../gnb.sa.band66.fr1.24PRB.usrpx300.conf | 1 - .../gnb.sa.band66.fr1.25PRB.usrpx300.conf | 1 - .../CONF/gnb.sa.band66.u0.25prb.rfsim.conf | 1 - .../gnb.sa.band77.106prb.fhi72.4x4-vvdn.conf | 1 - .../gnb.sa.band77.273prb.fhi72.2x2-vvdn.conf | 1 - .../gnb.sa.band77.273prb.fhi72.4x4-vvdn.conf | 1 - ...sa.band78.273prb.fhi72.4x2-benetel550.conf | 1 - ...78.273prb.fhi72.4x4-benetel550-mplane.conf | 1 - ...sa.band78.273prb.fhi72.4x4-benetel550.conf | 1 - ...gnb.sa.band78.273prb.fhi72.4x4-liteon.conf | 1 - ...b.sa.band78.273prb.fhi72.4x4-metanoia.conf | 1 - .../gnb.sa.band78.fr1.24PRB.usrpb210.conf | 1 - 33 files changed, 91 insertions(+), 115 deletions(-) diff --git a/ci-scripts/conf_files/gnb-vnf.sa.band66.u0.25prb.nfapi.conf b/ci-scripts/conf_files/gnb-vnf.sa.band66.u0.25prb.nfapi.conf index c14759c6af..6f914cf17a 100644 --- a/ci-scripts/conf_files/gnb-vnf.sa.band66.u0.25prb.nfapi.conf +++ b/ci-scripts/conf_files/gnb-vnf.sa.band66.u0.25prb.nfapi.conf @@ -16,8 +16,6 @@ gNBs = nr_cellid = 12345678L; ////////// Physical parameters: - - sib1_tda = 5; min_rxtxtime = 6; servingCellConfigCommon = ( diff --git a/ci-scripts/conf_files/gnb.sa.band261.u3.32prb.rfsim.conf b/ci-scripts/conf_files/gnb.sa.band261.u3.32prb.rfsim.conf index f2455bc2c6..c37acc89f5 100644 --- a/ci-scripts/conf_files/gnb.sa.band261.u3.32prb.rfsim.conf +++ b/ci-scripts/conf_files/gnb.sa.band261.u3.32prb.rfsim.conf @@ -18,8 +18,6 @@ gNBs = tr_s_preference = "local_mac" ////////// Physical parameters: - - sib1_tda = 15; min_rxtxtime = 6; servingCellConfigCommon = ( diff --git a/ci-scripts/conf_files/gnb.sa.band78.24prb.rfsim.conf b/ci-scripts/conf_files/gnb.sa.band78.24prb.rfsim.conf index afe40c733c..7153c90421 100644 --- a/ci-scripts/conf_files/gnb.sa.band78.24prb.rfsim.conf +++ b/ci-scripts/conf_files/gnb.sa.band78.24prb.rfsim.conf @@ -16,8 +16,6 @@ gNBs = nr_cellid = 12345678L; ////////// Physical parameters: - - sib1_tda = 15; min_rxtxtime = 6; servingCellConfigCommon = ( diff --git a/ci-scripts/conf_files/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf b/ci-scripts/conf_files/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf index 8d6a0e90cd..ee2930f8ad 100644 --- a/ci-scripts/conf_files/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf +++ b/ci-scripts/conf_files/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; # force_UL256qam_off = 1; servingCellConfigCommon = ( diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index 2fa56c2ce4..1ce15ec9d6 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -255,8 +255,6 @@ configuration](../RRC/rrc-usage.md) as well for SIB configuration. * `pusch_AntennaPorts` (default 1): number of antenna ports in PUSCH * `maxMIMO_layers` (default -1=unlimited): maximum number of MIMO layers to use in downlink -* `sib1_tda` (default 1): time domain allocation (TDA) indices to use for SIB1 - (38.214 section 5.1.2.1.1) * `do_CSIRS` (default 0): flag whether to use channel-state information reference signal (CSI-RS) * `do_SRS` (default 0): flag whether to use sounding reference signal (SRS) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 37b51b96fa..2c81fb5fa4 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -296,7 +296,6 @@ This allows to reuse HARQ processes immediately, but it breaks compatibility wit To enable this feature, the `disable_harq` flag has to be added to the gNB conf file in the section `gNBs.[0]` ``` ... - sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; // <-- diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index d70bf712b8..36ee2e4f0c 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -699,7 +699,6 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type) verify_gnb_param_notset(gnbp, GNB_PDSCH_ANTENNAPORTS_XP_IDX, GNB_CONFIG_STRING_PDSCHANTENNAPORTS_XP); verify_gnb_param_notset(gnbp, GNB_PUSCH_ANTENNAPORTS_IDX, GNB_CONFIG_STRING_PUSCHANTENNAPORTS); verify_gnb_param_notset(gnbp, GNB_MINRXTXTIME_IDX, GNB_CONFIG_STRING_MINRXTXTIME); - verify_gnb_param_notset(gnbp, GNB_SIB1_TDA_IDX, GNB_CONFIG_STRING_SIB1TDA); verify_gnb_param_notset(gnbp, GNB_DO_CSIRS_IDX, GNB_CONFIG_STRING_DOCSIRS); verify_gnb_param_notset(gnbp, GNB_DO_SRS_IDX, GNB_CONFIG_STRING_DOSRS); verify_gnb_param_notset(gnbp, GNB_FORCE256QAMOFF_IDX, GNB_CONFIG_STRING_FORCE256QAMOFF); @@ -1436,8 +1435,6 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) } config.minRXTXTIME = *GNBParamList.paramarray[0][GNB_MINRXTXTIME_IDX].iptr; LOG_I(GNB_APP, "minTXRXTIME %d\n", config.minRXTXTIME); - config.sib1_tda = *GNBParamList.paramarray[0][GNB_SIB1_TDA_IDX].iptr; - LOG_I(GNB_APP, "SIB1 TDA %d\n", config.sib1_tda); config.do_CSIRS = *GNBParamList.paramarray[0][GNB_DO_CSIRS_IDX].iptr; config.do_SRS = *GNBParamList.paramarray[0][GNB_DO_SRS_IDX].iptr; config.max_num_rsrp = *GNBParamList.paramarray[0][GNB_LIMIT_RSRP_REPORT_IDX].iptr; diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 4ec09d06f0..14cd6ba3c8 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -114,7 +114,6 @@ typedef enum { #define GNB_CONFIG_STRING_PDSCHANTENNAPORTS_N2 "pdsch_AntennaPorts_N2" #define GNB_CONFIG_STRING_PDSCHANTENNAPORTS_XP "pdsch_AntennaPorts_XP" #define GNB_CONFIG_STRING_PUSCHANTENNAPORTS "pusch_AntennaPorts" -#define GNB_CONFIG_STRING_SIB1TDA "sib1_tda" #define GNB_CONFIG_STRING_DOCSIRS "do_CSIRS" #define GNB_CONFIG_STRING_DOSRS "do_SRS" #define GNB_CONFIG_STRING_NRCELLID "nr_cellid" @@ -179,7 +178,6 @@ typedef enum { {GNB_CONFIG_STRING_PDSCHANTENNAPORTS_N2, "vert. log. antenna ports", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_PDSCHANTENNAPORTS_XP, "XP log. antenna ports", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_PUSCHANTENNAPORTS, NULL, 0, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \ -{GNB_CONFIG_STRING_SIB1TDA, NULL, 0, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_DOCSIRS, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_DOSRS, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_NRCELLID, NULL, 0, .u64ptr=NULL,.defint64val=1, TYPE_UINT64, 0}, \ @@ -225,30 +223,29 @@ typedef enum { #define GNB_PDSCH_ANTENNAPORTS_N2_IDX 14 #define GNB_PDSCH_ANTENNAPORTS_XP_IDX 15 #define GNB_PUSCH_ANTENNAPORTS_IDX 16 -#define GNB_SIB1_TDA_IDX 17 -#define GNB_DO_CSIRS_IDX 18 -#define GNB_DO_SRS_IDX 19 -#define GNB_NRCELLID_IDX 20 -#define GNB_MINRXTXTIME_IDX 21 -#define GNB_ULPRBBLACKLIST_IDX 22 -#define GNB_UMONDEFAULTDRB_IDX 23 -#define GNB_FORCE256QAMOFF_IDX 24 -#define GNB_ENABLE_SDAP_IDX 25 -#define GNB_DRBS 26 -#define GNB_GNB_DU_ID_IDX 27 -#define GNB_GNB_CU_UP_ID_IDX 28 -#define GNB_USE_DELTA_MCS_IDX 29 -#define GNB_FORCEUL256QAMOFF_IDX 30 -#define GNB_MAXMIMOLAYERS_IDX 31 -#define GNB_DISABLE_HARQ_IDX 32 -#define GNB_NUM_DL_HARQ_IDX 33 -#define GNB_NUM_UL_HARQ_IDX 34 -#define GNB_UESS_AGG_LEVEL_LIST_IDX 35 -#define GNB_CU_SIBS_IDX 36 -#define GNB_DU_SIBS_IDX 37 -#define GNB_CONFIG_REP_IDX 38 -#define GNB_1ST_ACTIVE_BWP_IDX 39 -#define GNB_LIMIT_RSRP_REPORT_IDX 40 +#define GNB_DO_CSIRS_IDX 17 +#define GNB_DO_SRS_IDX 18 +#define GNB_NRCELLID_IDX 19 +#define GNB_MINRXTXTIME_IDX 20 +#define GNB_ULPRBBLACKLIST_IDX 21 +#define GNB_UMONDEFAULTDRB_IDX 22 +#define GNB_FORCE256QAMOFF_IDX 23 +#define GNB_ENABLE_SDAP_IDX 24 +#define GNB_DRBS 25 +#define GNB_GNB_DU_ID_IDX 26 +#define GNB_GNB_CU_UP_ID_IDX 27 +#define GNB_USE_DELTA_MCS_IDX 28 +#define GNB_FORCEUL256QAMOFF_IDX 29 +#define GNB_MAXMIMOLAYERS_IDX 30 +#define GNB_DISABLE_HARQ_IDX 31 +#define GNB_NUM_DL_HARQ_IDX 32 +#define GNB_NUM_UL_HARQ_IDX 33 +#define GNB_UESS_AGG_LEVEL_LIST_IDX 34 +#define GNB_CU_SIBS_IDX 35 +#define GNB_DU_SIBS_IDX 36 +#define GNB_CONFIG_REP_IDX 37 +#define GNB_1ST_ACTIVE_BWP_IDX 38 +#define GNB_LIMIT_RSRP_REPORT_IDX 39 #define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD} #define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32} @@ -287,7 +284,6 @@ typedef enum { { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ - { .s5 = { NULL } }, \ { .s1 = { config_check_intval, NUM_DL_HARQ_OKVALUES,8 } }, \ { .s1 = { config_check_intval, NUM_UL_HARQ_OKVALUES,2 } }, \ { .s5 = { NULL } }, \ diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index d8c4ee04ba..600c4b7130 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -255,7 +255,7 @@ void schedule_nr_mib(module_id_t module_idP, frame_t frameP, slot_t slotP, nfapi } } -static void update_rb_mcs_tbs(NR_sched_pdsch_t *pdsch, uint32_t num_total_bytes, uint16_t *vrb_map) +static bool update_rb_mcs_tbs(NR_sched_pdsch_t *pdsch, uint32_t num_total_bytes, uint16_t *vrb_map) { const NR_tda_info_t *tda_info = &pdsch->tda_info; @@ -296,12 +296,15 @@ static void update_rb_mcs_tbs(NR_sched_pdsch_t *pdsch, uint32_t num_total_bytes, break; } } - AssertFatal(pdsch->tb_size >= num_total_bytes, - "Couldn't allocate enough resources for %d bytes in SIB PDSCH (rbStart %d, rbSize %d, bwpSize %d)\n", - num_total_bytes, - pdsch->rbStart, - pdsch->rbSize, - bwpSize); + if (pdsch->tb_size < num_total_bytes) { + LOG_D(NR_MAC, + "Couldn't allocate enough resources for %d bytes in SIB PDSCH (rbStart %d, rbSize %d, bwpSize %d)\n", + num_total_bytes, + pdsch->rbStart, + pdsch->rbSize, + bwpSize); + return false; + } LOG_D(NR_MAC, "mcs=%i, startSymbolIndex = %i, nrOfSymbols = %i, rbSize = %i, TBS = %i, dmrs_length %d, N_PRB_DMRS = %d, mappingtype = %d\n", @@ -313,6 +316,7 @@ static void update_rb_mcs_tbs(NR_sched_pdsch_t *pdsch, uint32_t num_total_bytes, pdsch->dmrs_parms.N_DMRS_SLOT, N_PRB_DMRS, tda_info->mapping_type); + return true; } static NR_sched_pdsch_t schedule_control_sib1(gNB_MAC_INST *gNB_mac, @@ -342,11 +346,12 @@ static NR_sched_pdsch_t schedule_control_sib1(gNB_MAC_INST *gNB_mac, uint8_t nr_of_candidates = 0; - for (int i=0; i<3; i++) { + for (int i = 0; i < 3; i++) { find_aggregation_candidates(&gNB_mac->sched_ctrlCommon->aggregation_level, &nr_of_candidates, gNB_mac->sched_ctrlCommon->search_space,4<0) break; // choosing the lower value of aggregation level available + if (nr_of_candidates > 0) + break; // choosing the lower value of aggregation level available } - AssertFatal(nr_of_candidates>0,"nr_of_candidates is 0\n"); + AssertFatal(nr_of_candidates > 0, "nr_of_candidates is 0\n"); gNB_mac->sched_ctrlCommon->cce_index = find_pdcch_candidate(gNB_mac, CC_id, gNB_mac->sched_ctrlCommon->aggregation_level, @@ -358,18 +363,21 @@ static NR_sched_pdsch_t schedule_control_sib1(gNB_MAC_INST *gNB_mac, AssertFatal(gNB_mac->sched_ctrlCommon->cce_index >= 0, "Could not find CCE for coreset0\n"); - update_rb_mcs_tbs(&pdsch, gNB_mac->sched_ctrlCommon->num_total_bytes, vrb_map); + bool success = update_rb_mcs_tbs(&pdsch, gNB_mac->sched_ctrlCommon->num_total_bytes, vrb_map); + if (success) { + // Mark the corresponding RBs as used + fill_pdcch_vrb_map(gNB_mac, + CC_id, + pdcch, + gNB_mac->sched_ctrlCommon->cce_index, + gNB_mac->sched_ctrlCommon->aggregation_level, + beam); + for (int rb = 0; rb < pdsch.rbSize; rb++) { + vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols); + } + } else + pdsch.tb_size = 0; // to signal we couldn't allocate TBS - // Mark the corresponding RBs as used - fill_pdcch_vrb_map(gNB_mac, - CC_id, - pdcch, - gNB_mac->sched_ctrlCommon->cce_index, - gNB_mac->sched_ctrlCommon->aggregation_level, - beam); - for (int rb = 0; rb < pdsch.rbSize; rb++) { - vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols); - } return pdsch; } @@ -474,12 +482,8 @@ void schedule_nr_sib1(module_id_t module_idP, // TODO: Get these values from RRC const int CC_id = 0; uint8_t candidate_idx = 0; - gNB_MAC_INST *gNB_mac = RC.nrmac[module_idP]; NR_ServingCellConfigCommon_t *scc = gNB_mac->common_channels[CC_id].ServingCellConfigCommon; - - int time_domain_allocation = gNB_mac->radio_config.sib1_tda; - int L_max; switch (scc->ssb_PositionsInBurst->present) { case 1: @@ -492,8 +496,7 @@ void schedule_nr_sib1(module_id_t module_idP, L_max = 64; break; default: - AssertFatal(0,"SSB bitmap size value %d undefined (allowed values 1,2,3)\n", - scc->ssb_PositionsInBurst->present); + AssertFatal(false, "SSB bitmap size value %d undefined (allowed values 1,2,3)\n", scc->ssb_PositionsInBurst->present); } for (int i = 0; i < L_max; i++) { @@ -512,38 +515,51 @@ void schedule_nr_sib1(module_id_t module_idP, int beam_index = get_beam_from_ssbidx(gNB_mac, i); NR_beam_alloc_t beam = beam_allocation_procedure(&gNB_mac->beam_info, frameP, slotP, beam_index, n_slots_frame); AssertFatal(beam.idx >= 0, "Cannot allocate SIB1 corresponding to SSB %d in any available beam\n", i); + NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; LOG_D(NR_MAC,"(%d.%d) SIB1 transmission: ssb_index %d\n", frameP, slotP, type0_PDCCH_CSS_config->ssb_index); - default_table_type_t table_type = get_default_table_type(type0_PDCCH_CSS_config->type0_pdcch_ss_mux_pattern); - // assuming normal CP - NR_tda_info_t tda_info = get_info_from_tda_tables(table_type, time_domain_allocation, scc->dmrs_TypeA_Position, true); - - AssertFatal((tda_info.startSymbolIndex + tda_info.nrOfSymbols) < 14, - "SIB1 TDA %d would cause overlap with CSI-RS. Please select a different SIB1 TDA.\n", - time_domain_allocation); - - NR_pdsch_dmrs_t dmrs_parms = get_dl_dmrs_params(scc, NULL, &tda_info, 1); - NR_sched_pdcch_t sched_pdcch = set_pdcch_structure(NULL, gNB_mac->sched_ctrlCommon->search_space, gNB_mac->sched_ctrlCommon->coreset, scc, NULL, type0_PDCCH_CSS_config); + int time_domain_allocation = -1; + NR_sched_pdsch_t sched_pdsch = {0}; + for (int t = 0; t < 16; t++) { + // If the PDSCH was scheduled with SI-RNTI in PDCCH Type0 common search space, + // the UE may assume that this PDSCH resource allocation is not applied (Table 5.1.2.1.1-4 and 5 in 38.214) + int row = t + 1; + if (table_type == defaultB && (row == 12 || row == 13 || row == 14)) + continue; + if (table_type == defaultC && (row == 1 || row == 6 || row == 7 ||row > 12)) + continue; + // assuming normal CP + NR_tda_info_t tda_info = get_info_from_tda_tables(table_type, t, scc->dmrs_TypeA_Position, true); + // SIB1 TDA going to the last symbol would cause overlap with CSI-RS + if (!tda_info.valid_tda || tda_info.startSymbolIndex + tda_info.nrOfSymbols == 14) + continue; + if (tda_info.startSymbolIndex < type0_PDCCH_CSS_config->first_symbol_index + type0_PDCCH_CSS_config->num_symbols) + continue; - NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; - // Configure sched_ctrlCommon for SIB1 - NR_sched_pdsch_t sched_pdsch = schedule_control_sib1(gNB_mac, - CC_id, - &sched_pdcch, - type0_PDCCH_CSS_config, - time_domain_allocation, - &dmrs_parms, - &tda_info, - candidate_idx, - beam.idx, - cc->sib1_bcch_length); - + NR_pdsch_dmrs_t dmrs_parms = get_dl_dmrs_params(scc, NULL, &tda_info, 1); + // Configure sched_ctrlCommon for SIB1 + sched_pdsch = schedule_control_sib1(gNB_mac, + CC_id, + &sched_pdcch, + type0_PDCCH_CSS_config, + t, + &dmrs_parms, + &tda_info, + candidate_idx, + beam.idx, + cc->sib1_bcch_length); + if (sched_pdsch.tb_size > 0) { + time_domain_allocation = t; + break; + } + } + AssertFatal(time_domain_allocation > 0, "Couldn't select any TDA for SIB1\n"); nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; int pdu_index = gNB_mac->pdu_index[0]++; nr_fill_nfapi_dl_SIB_pdu(gNB_mac, @@ -673,7 +689,8 @@ static void other_sib_sched_control(module_id_t module_idP, uint16_t *vrb_map = cc->vrb_map[beam.idx]; uint8_t *sib_bcch_pdu = cc->other_sib_bcch_pdu[payload_idx]; int num_total_bytes = cc->other_sib_bcch_length[payload_idx]; - update_rb_mcs_tbs(&sched_pdsch_otherSI, num_total_bytes, vrb_map); + bool success = update_rb_mcs_tbs(&sched_pdsch_otherSI, num_total_bytes, vrb_map); + AssertFatal(success, "Couldn't allocate TBS for other SIB\n"); for (int rb = 0; rb < sched_pdsch_otherSI.rbSize; rb++) { vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info.startSymbolIndex, tda_info.nrOfSymbols); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index ca1e217099..8863098de5 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -186,7 +186,6 @@ typedef enum { } nr_config_report_type_t; typedef struct nr_mac_config_t { - int sib1_tda; nr_pdsch_AntennaPorts_t pdsch_AntennaPorts; int pusch_AntennaPorts; int minRXTXTIME; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf index 1d3e0e70d0..8c200c72fd 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci0.conf @@ -19,7 +19,6 @@ gNBs = ////////// Physical parameters: min_rxtxtime = 4; - sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf index cdcd1a59fd..f87cc2bb8c 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band66.25prb.rfsim.pci1.conf @@ -19,7 +19,6 @@ gNBs = ////////// Physical parameters: min_rxtxtime = 4; - sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-benetel650.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-benetel650.conf index 7a6becbd8c..1652758e4e 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-benetel650.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.4x4-benetel650.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; force_UL256qam_off = 1; # maxMIMO_layers = 2; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650-mplane.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650-mplane.conf index 3dd36be9c7..eee6f875c9 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650-mplane.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650-mplane.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 8; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; force_UL256qam_off = 1; maxMIMO_layers = 2; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650.conf index 18829188e3..fdad2726c2 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-du.sa.band77.273prb.fhi72.8x8-benetel650_650.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 8; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; force_UL256qam_off = 1; maxMIMO_layers = 2; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-vnf.sa.band78.106prb.nfapi.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-vnf.sa.band78.106prb.nfapi.conf index aea3176456..467873a36f 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-vnf.sa.band78.106prb.nfapi.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-vnf.sa.band78.106prb.nfapi.conf @@ -22,7 +22,6 @@ gNBs = pusch_AntennaPorts = 1; min_rxtxtime = 6; enable_sdap = 0; - #sib1_tda = 0; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf index a6ca5decbe..10caa403d3 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band254.ntn.mu1.24prb.rfsim.conf @@ -16,7 +16,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; cu_sibs = [2]; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf index c811f0d64e..1684a375d0 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band255.ntn.mu0.25prb.rfsim.conf @@ -16,7 +16,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; cu_sibs = [2]; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf index 3a472ff7a2..c20188594d 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band256.ntn.mu0.25prb.rfsim.conf @@ -16,7 +16,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 5; min_rxtxtime = 6; disable_harq = 1; cu_sibs = [2]; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band257.u3.32prb.usrpx410.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band257.u3.32prb.usrpx410.conf index 933cddfcb9..0c949fdccb 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band257.u3.32prb.usrpx410.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band257.u3.32prb.usrpx410.conf @@ -21,7 +21,6 @@ gNBs = pusch_AntennaPorts = 1; do_CSIRS = 1; do_SRS = 1; - sib1_tda = 15; min_rxtxtime = 6; servingCellConfigCommon = ( diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.52PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.52PRB.usrpb210.conf index a02394b5b2..bb5263aa9a 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.52PRB.usrpb210.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band41.fr1.52PRB.usrpb210.conf @@ -22,7 +22,6 @@ gNBs = do_CSIRS = 1; do_SRS = 1; - sib1_tda = 15; min_rxtxtime = 6; servingCellConfigCommon = ( diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.24PRB.usrpx300.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.24PRB.usrpx300.conf index 6b9b104bc0..f17e695e8a 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.24PRB.usrpx300.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.24PRB.usrpx300.conf @@ -32,7 +32,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 5; min_rxtxtime = 6; # disable_harq = 1; # num_dlharq = 32; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.25PRB.usrpx300.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.25PRB.usrpx300.conf index 560b33b0e9..d5b0559f0c 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.25PRB.usrpx300.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.fr1.25PRB.usrpx300.conf @@ -32,7 +32,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 5; min_rxtxtime = 6; # disable_harq = 1; # num_dlharq = 32; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.u0.25prb.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.u0.25prb.rfsim.conf index 9458c2b272..428b6cb27c 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.u0.25prb.rfsim.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band66.u0.25prb.rfsim.conf @@ -17,7 +17,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 5; min_rxtxtime = 6; servingCellConfigCommon = ( diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.106prb.fhi72.4x4-vvdn.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.106prb.fhi72.4x4-vvdn.conf index 753670a9eb..f7f8390b33 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.106prb.fhi72.4x4-vvdn.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.106prb.fhi72.4x4-vvdn.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.2x2-vvdn.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.2x2-vvdn.conf index 38580730f2..d68e67c96f 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.2x2-vvdn.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.2x2-vvdn.conf @@ -21,7 +21,6 @@ gNBs = pusch_AntennaPorts = 2; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.4x4-vvdn.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.4x4-vvdn.conf index d55bc6dbda..7e13bf7dec 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.4x4-vvdn.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band77.273prb.fhi72.4x4-vvdn.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x2-benetel550.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x2-benetel550.conf index 75fe0e6fdd..715dcda78c 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x2-benetel550.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x2-benetel550.conf @@ -21,7 +21,6 @@ gNBs = pusch_AntennaPorts = 2; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; # force_UL256qam_off = 1; servingCellConfigCommon = ( diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550-mplane.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550-mplane.conf index ed996cac27..241ca9f23a 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550-mplane.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550-mplane.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; # force_UL256qam_off = 1; servingCellConfigCommon = ( diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf index d8ca02a3be..2788e7f5f5 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-benetel550.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; # force_UL256qam_off = 1; servingCellConfigCommon = ( diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-liteon.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-liteon.conf index 3c9780130f..2419df21bb 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-liteon.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-liteon.conf @@ -23,7 +23,6 @@ gNBs = pusch_AntennaPorts = 4; do_CSIRS = 1; do_SRS = 0; - sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-metanoia.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-metanoia.conf index 13617fb578..322d61b2b3 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-metanoia.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.273prb.fhi72.4x4-metanoia.conf @@ -21,7 +21,6 @@ gNBs = do_CSIRS = 1; do_SRS = 0; force_UL256qam_off = 1; - # sib1_tda = 15; servingCellConfigCommon = ( { diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.24PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.24PRB.usrpb210.conf index 1398020417..63c63bd80e 100644 --- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.24PRB.usrpb210.conf +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.24PRB.usrpb210.conf @@ -17,7 +17,6 @@ gNBs = ////////// Physical parameters: - sib1_tda = 15; min_rxtxtime = 6; servingCellConfigCommon = ( From b0f402dfdc30e0b6dca07748e194ee62b1c836b1 Mon Sep 17 00:00:00 2001 From: Raymond Knopp Date: Sat, 20 Dec 2025 21:48:09 +0100 Subject: [PATCH 21/43] hotfix for when there are more physical TX antenna ports (ru->nb_tx) than logical antenna ports (config->carrier_config.num_tx_ant.value). --- openair1/SCHED_NR/nr_ru_procedures.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openair1/SCHED_NR/nr_ru_procedures.c b/openair1/SCHED_NR/nr_ru_procedures.c index 6bc7cb933a..03631e3c7e 100644 --- a/openair1/SCHED_NR/nr_ru_procedures.c +++ b/openair1/SCHED_NR/nr_ru_procedures.c @@ -199,10 +199,11 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx) if (nr_slot_select(cfg,frame_tx,slot_tx) == NR_UPLINK_SLOT) return; + int Ptx = cfg->carrier_config.num_tx_ant.value; // If there is no digital beamforming we just need to copy the data to RU if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) { for (int b = 0; b < ru->num_beams_period; b++) { - for (int i = 0; i < ru->nb_tx; ++i) { + for (int i = 0; i < Ptx; ++i) { int tx_idx = i + b * ru->nb_tx; memcpy((void*)ru->common.txdataF_BF[tx_idx], (void*)&gNB->common_vars.txdataF[b][i][txdataF_offset], From 74690ceb1df4928ef1cde39ab9966fb978818aac Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 15 Dec 2025 15:46:59 +0100 Subject: [PATCH 22/43] fix CORESET start PRB according to standard at OAI UE --- .../nfapi/public_inc/fapi_nr_ue_interface.h | 1 + openair1/PHY/NR_UE_TRANSPORT/dci_nr.c | 23 +++++++++---------- .../NR_MAC_UE/nr_ue_dci_configuration.c | 9 ++++++++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index 37337e82d3..f3994e22a5 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -73,6 +73,7 @@ typedef struct { typedef struct { /// frequency_domain_resource; uint8_t frequency_domain_resource[6]; + uint32_t rb_offset; uint8_t StartSymbolIndex; uint16_t StartSymbolBitmap; uint8_t duration; diff --git a/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c b/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c index f4ab076921..822389ebdc 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c +++ b/openair1/PHY/NR_UE_TRANSPORT/dci_nr.c @@ -204,6 +204,7 @@ static void nr_pdcch_extract_rbs_single(uint32_t rxdataF_sz, c16_t dl_ch_estimates_ext[][arraySz], NR_DL_FRAME_PARMS *frame_parms, uint8_t *coreset_freq_dom, + uint32_t rb_offset, uint32_t coreset_nbr_rb, uint32_t n_BWP_start) { @@ -250,7 +251,7 @@ static void nr_pdcch_extract_rbs_single(uint32_t rxdataF_sz, continue; } for (int rb = 0; rb < 6; rb++) { - int c_rb = rb_group * 6 + rb; + int c_rb = rb_group * 6 + rb + rb_offset; c16_t *rxF = NULL; if ((frame_parms->N_RB_DL & 1) == 0) { if ((c_rb + n_BWP_start) < frame_parms->N_RB_DL / 2) @@ -338,10 +339,9 @@ static void nr_rx_pdcch_symbol(PHY_VARS_NR_UE *ue, fapi_nr_coreset_t *coreset = &phy_pdcch_config->pdcch_config[ss_idx].coreset; int32_t pdcch_est_size = ceil_mod(fp->ofdm_symbol_size + LTE_CE_FILTER_LENGTH, 16); __attribute__((aligned(16))) c16_t pdcch_dl_ch_estimates[fp->nb_antennas_rx][pdcch_est_size]; - int n_rb; - int rb_offset; - get_coreset_rballoc(coreset->frequency_domain_resource, &n_rb, &rb_offset); - + int n_rb, cset_start; + get_coreset_rballoc(coreset->frequency_domain_resource, &n_rb, &cset_start); + int rb_offset = cset_start + coreset->rb_offset; unsigned short scrambling_id = coreset->pdcch_dmrs_scrambling_id; int dmrs_ref = 0; if (coreset->CoreSetType == NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG) @@ -376,6 +376,7 @@ static void nr_rx_pdcch_symbol(PHY_VARS_NR_UE *ue, pdcch_dl_ch_estimates_ext, fp, coreset->frequency_domain_resource, + coreset->rb_offset, n_rb, phy_pdcch_config->pdcch_config[ss_idx].BWPStart); @@ -509,10 +510,8 @@ void nr_pdcch_dci_indication(const UE_nr_rxtx_proc_t *proc, uint8_t unused_start_symb[NR_SYMBOLS_PER_SLOT] = {0}; const int num_monitoring_occ = get_pdcch_mon_occasions_slot(rel15, unused_start_symb); const int llr_stride = llr_size / rel15->coreset.duration; - - int n_rb; - int rb_offset; - get_coreset_rballoc(rel15->coreset.frequency_domain_resource, &n_rb, &rb_offset); + int n_rb, cset_start; + get_coreset_rballoc(rel15->coreset.frequency_domain_resource, &n_rb, &cset_start); for (int m = 0; m < num_monitoring_occ; m++) { /// PDCCH/DCI e-sequence (input to rate matching). @@ -643,9 +642,9 @@ static void nr_dci_decoding_procedure(PHY_VARS_NR_UE *ue, .ss_type = rel15->ss_type_options[k], .coreset_type = rel15->coreset.CoreSetType, }; - int n_rb, rb_offset; - get_coreset_rballoc(rel15->coreset.frequency_domain_resource, &n_rb, &rb_offset); - dci->cset_start = rel15->BWPStart + rb_offset; + int n_rb, cset_start; + get_coreset_rballoc(rel15->coreset.frequency_domain_resource, &n_rb, &cset_start); + dci->cset_start = rel15->BWPStart + cset_start + rel15->coreset.rb_offset; dci->payloadSize = dci_length; memcpy(dci->payloadBits, dci_estimation, (dci_length + 7) / 8); dci_ind->number_of_dcis++; diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c index 1f84b11a48..af6bf37aff 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -122,9 +122,18 @@ static void config_dci_pdu(NR_UE_MAC_INST_t *mac, if(coreset_id > 0) { coreset = ue_get_coreset(pdcch_config, coreset_id); rel15->coreset.CoreSetType = NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG; + if (coreset->ext1 && coreset->ext1->rb_Offset_r16) + rel15->coreset.rb_offset = *coreset->ext1->rb_Offset_r16; + else { + // first common RB of the first group of 6 PRBs has common RB index equal to + // 6 * ⌈BWP_start / 6⌉ if rb-Offset is not provided + int start_common = (current_DL_BWP->BWPStart + 5) / 6 * 6; + rel15->coreset.rb_offset = start_common - current_DL_BWP->BWPStart; + } } else { coreset = mac->coreset0; rel15->coreset.CoreSetType = NFAPI_NR_CSET_CONFIG_MIB_SIB1; + rel15->coreset.rb_offset = 0; } rel15->coreset.duration = coreset->duration; From 2a3a27a48f25282fa4fa0e80d5411bb8753710ed Mon Sep 17 00:00:00 2001 From: francescomani Date: Mon, 15 Dec 2025 16:14:50 +0100 Subject: [PATCH 23/43] fix CORESET start PRB according to standard at OAI gNB --- openair1/PHY/NR_TRANSPORT/nr_dci.c | 12 ++++++++---- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 3 ++- .../LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openair1/PHY/NR_TRANSPORT/nr_dci.c b/openair1/PHY/NR_TRANSPORT/nr_dci.c index 28d22b31bb..12bff45dee 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_dci.c +++ b/openair1/PHY/NR_TRANSPORT/nr_dci.c @@ -61,9 +61,13 @@ void nr_generate_dci(PHY_VARS_gNB *gNB, int reg_list[MAX_DCI_CORESET][NR_MAX_PDCCH_AGG_LEVEL * NR_NB_REG_PER_CCE]; nr_fill_reg_list(reg_list, pdcch_pdu_rel15); // compute rb_offset and n_prb based on frequency allocation - int rb_offset; - int n_rb; - get_coreset_rballoc(pdcch_pdu_rel15->FreqDomainResource,&n_rb,&rb_offset); + int n_rb, cset_start; + get_coreset_rballoc(pdcch_pdu_rel15->FreqDomainResource, &n_rb, &cset_start); + int additional_offset = 0; + // first common RB of the first group of 6 PRBs has common RB index equal to 6 * ⌈BWP_start / 6⌉ + if (pdcch_pdu_rel15->CoreSetType == 1) + additional_offset = (pdcch_pdu_rel15->BWPStart + 5) / 6 * 6 - pdcch_pdu_rel15->BWPStart; + int rb_offset = cset_start + additional_offset; uint16_t cset_start_sc = frame_parms->first_carrier_offset + (pdcch_pdu_rel15->BWPStart + rb_offset) * NR_NB_SC_PER_RB; int idx1 = pdcch_pdu_rel15->StartSymbolIndex+pdcch_pdu_rel15->DurationSymbols; int idx2 = (((n_rb + rb_offset + pdcch_pdu_rel15->BWPStart) * 3) + 15) & ~15; @@ -194,7 +198,7 @@ void nr_generate_dci(PHY_VARS_gNB *gNB, // dmrs index depends on reference point for k according to 38.211 7.4.1.3.2 int dmrs_idx; if (pdcch_pdu_rel15->CoreSetType == NFAPI_NR_CSET_CONFIG_PDCCH_CONFIG) - dmrs_idx = (reg_list[d][reg_count] + pdcch_pdu_rel15->BWPStart) * 3; + dmrs_idx = (reg_list[d][reg_count] + pdcch_pdu_rel15->BWPStart + rb_offset) * 3; else dmrs_idx = (reg_list[d][reg_count] + rb_offset) * 3; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 00c41953a1..43d36f0515 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -404,7 +404,8 @@ bwp_info_t get_pdsch_bwp_start_size(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE) if (sched_ctrl->coreset->controlResourceSetId == 0) { bwp_info.bwpStart = nr_mac->cset0_bwp_start; } else { - bwp_info.bwpStart = dl_bwp->BWPStart + sched_ctrl->sched_pdcch.rb_start; + int additional_offset = (dl_bwp->BWPStart + 5) / 6 * 6 - dl_bwp->BWPStart; + bwp_info.bwpStart = dl_bwp->BWPStart + sched_ctrl->sched_pdcch.rb_start + additional_offset; } if (nr_mac->cset0_bwp_size > 0) { bwp_info.bwpSize = min(dl_bwp->BWPSize, nr_mac->cset0_bwp_size); diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index f9a099d79b..97feb06b81 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -454,6 +454,7 @@ static NR_SearchSpace_t *get_searchspace(NR_ServingCellConfigCommon_t *scc, /// @param rb_start Output parameter for the starting resource block index of the CORESET static void get_coreset_rb_params(const NR_ControlResourceSet_t *coreset, uint16_t *n_rb, uint16_t *rb_start) { + AssertFatal(!coreset->ext1 || !coreset->ext1->rb_Offset_r16, "rb-Offset in coreset configuration not handled\n"); *n_rb = 0; *rb_start = 0; From 61d0559445e9580dc0f3bfdd2fb2dbfe91b77bd2 Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 16 Dec 2025 13:53:18 +0100 Subject: [PATCH 24/43] take into account effective CSET start RB to define frequencyDomainResources --- openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c | 31 +++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c index 72bc611e2c..538d244dd8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c +++ b/openair2/LAYER2/NR_MAC_gNB/nr_radio_config.c @@ -273,21 +273,23 @@ NR_SearchSpace_t *rrc_searchspace_config(bool is_common, return ss; } -static NR_ControlResourceSet_t *get_coreset_config(int bwp_id, int curr_bwp, uint64_t ssb_bitmap) +static NR_ControlResourceSet_t *get_coreset_config(int bwp_id, int bwp_start, int bwp_size, uint64_t ssb_bitmap) { NR_ControlResourceSet_t *coreset = calloc(1, sizeof(*coreset)); AssertFatal(coreset != NULL, "out of memory\n"); // frequency domain resources depending on BWP size + int additional_offset = (bwp_start + 5) / 6 * 6 - bwp_start; + int eff_bwp_size = bwp_size - additional_offset; coreset->frequencyDomainResources.buf = calloc(1,6); - coreset->frequencyDomainResources.buf[0] = (curr_bwp < 48) ? 0xf0 : 0xff; - coreset->frequencyDomainResources.buf[1] = (curr_bwp < 96) ? 0x00 : 0xff; - coreset->frequencyDomainResources.buf[2] = (curr_bwp < 144) ? 0x00 : 0xff; - coreset->frequencyDomainResources.buf[3] = (curr_bwp < 192) ? 0x00 : 0xff; - coreset->frequencyDomainResources.buf[4] = (curr_bwp < 240) ? 0x00 : 0xff; + coreset->frequencyDomainResources.buf[0] = (eff_bwp_size < 48) ? 0xf0 : 0xff; + coreset->frequencyDomainResources.buf[1] = (eff_bwp_size < 96) ? 0x00 : 0xff; + coreset->frequencyDomainResources.buf[2] = (eff_bwp_size < 144) ? 0x00 : 0xff; + coreset->frequencyDomainResources.buf[3] = (eff_bwp_size < 192) ? 0x00 : 0xff; + coreset->frequencyDomainResources.buf[4] = (eff_bwp_size < 240) ? 0x00 : 0xff; coreset->frequencyDomainResources.buf[5] = 0x00; coreset->frequencyDomainResources.size = 6; coreset->frequencyDomainResources.bits_unused = 3; - coreset->duration = (curr_bwp < 48) ? 2 : 1; + coreset->duration = (eff_bwp_size < 48) ? 2 : 1; coreset->cce_REG_MappingType.present = NR_ControlResourceSet__cce_REG_MappingType_PR_nonInterleaved; coreset->precoderGranularity = NR_ControlResourceSet__precoderGranularity_sameAsREG_bundle; @@ -1746,10 +1748,10 @@ static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t bwp->bwp_Common->pdcch_ConfigCommon->choice.setup = calloc(1,sizeof(*bwp->bwp_Common->pdcch_ConfigCommon->choice.setup)); bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->controlResourceSetZero = NULL; - int curr_bwp = NRRIV2BW(bwp->bwp_Common->genericParameters.locationAndBandwidth,MAX_BWP_SIZE); - + int bwp_size = NRRIV2BW(bwp->bwp_Common->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); + int bwp_start = NRRIV2PRBOFFSET(bwp->bwp_Common->genericParameters.locationAndBandwidth, MAX_BWP_SIZE); uint64_t ssb_bitmap = get_ssb_bitmap(scc); - NR_ControlResourceSet_t *coreset = get_coreset_config(bwp->bwp_Id, curr_bwp, ssb_bitmap); + NR_ControlResourceSet_t *coreset = get_coreset_config(bwp->bwp_Id, bwp_start, bwp_size, ssb_bitmap); bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->commonControlResourceSet = coreset; bwp->bwp_Common->pdcch_ConfigCommon->choice.setup->searchSpaceZero=NULL; @@ -1790,7 +1792,7 @@ static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t nr_rrc_config_dl_tda(bwp->bwp_Common->pdsch_ConfigCommon->choice.setup->pdsch_TimeDomainAllocationList, get_frame_type((int)*scc->downlinkConfigCommon->frequencyInfoDL->frequencyBandList.list.array[0], *scc->ssbSubcarrierSpacing), scc->tdd_UL_DL_ConfigurationCommon, - curr_bwp); + bwp_size); if (!bwp->bwp_Dedicated) { bwp->bwp_Dedicated=calloc(1,sizeof(*bwp->bwp_Dedicated)); @@ -1803,7 +1805,7 @@ static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t // coreset2 is identical to coreset above, but reallocated to prevent double // frees - NR_ControlResourceSet_t *coreset2 = get_coreset_config(bwp->bwp_Id, curr_bwp, ssb_bitmap); + NR_ControlResourceSet_t *coreset2 = get_coreset_config(bwp->bwp_Id, bwp_start, bwp_size, ssb_bitmap); asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->controlResourceSetToAddModList->list, coreset2); int rrc_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS]; int num_cces = get_coreset_num_cces(coreset2->frequencyDomainResources.buf, coreset2->duration); @@ -3332,8 +3334,9 @@ static NR_BWP_DownlinkDedicated_t *configure_initial_dl_bwp(const NR_ServingCell pdcch_Config->searchSpacesToAddModList = calloc(1, sizeof(*pdcch_Config->searchSpacesToAddModList)); pdcch_Config->controlResourceSetToAddModList = calloc(1, sizeof(*pdcch_Config->controlResourceSetToAddModList)); NR_BWP_t *genericParameters = &scc->downlinkConfigCommon->initialDownlinkBWP->genericParameters; - int curr_bwp = NRRIV2BW(genericParameters->locationAndBandwidth, MAX_BWP_SIZE); - NR_ControlResourceSet_t *coreset = get_coreset_config(0, curr_bwp, bitmap); + int bwp_size = NRRIV2BW(genericParameters->locationAndBandwidth, MAX_BWP_SIZE); + int bwp_start = NRRIV2PRBOFFSET(genericParameters->locationAndBandwidth, MAX_BWP_SIZE); + NR_ControlResourceSet_t *coreset = get_coreset_config(0, bwp_start, bwp_size, bitmap); asn1cSeqAdd(&pdcch_Config->controlResourceSetToAddModList->list, coreset); int css_num_agg_level_candidates[NUM_PDCCH_AGG_LEVELS]; From e9d410dc280763344d3b4777f0390613c0261301 Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 17 Dec 2025 09:06:19 +0100 Subject: [PATCH 25/43] change in CI config file for BWP testing to have 1 BWP which doesn't start from a multiple of 6 --- ci-scripts/conf_files/gnb-du.sa.band78.106prb.usrpb200.conf | 2 +- ci-scripts/xml_files/container_sa_e1_b200_quectel.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.106prb.usrpb200.conf b/ci-scripts/conf_files/gnb-du.sa.band78.106prb.usrpb200.conf index 58ba62696a..e314a1f299 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.106prb.usrpb200.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.106prb.usrpb200.conf @@ -149,7 +149,7 @@ gNBs = bwp_list = ( { scs = 1; bwpStart = 0; bwpSize = 106;}, { scs = 1; bwpStart = 0; bwpSize = 36;}, - { scs = 1; bwpStart = 36; bwpSize = 70;} + { scs = 1; bwpStart = 40; bwpSize = 50;} ); # ------- SCTP definitions diff --git a/ci-scripts/xml_files/container_sa_e1_b200_quectel.xml b/ci-scripts/xml_files/container_sa_e1_b200_quectel.xml index 836143431a..d0a65801a3 100644 --- a/ci-scripts/xml_files/container_sa_e1_b200_quectel.xml +++ b/ci-scripts/xml_files/container_sa_e1_b200_quectel.xml @@ -183,7 +183,7 @@ -t 10 -R idefix sabox-nepes - 85 + 65 From d7cc1c2c1db047a54556a0864632e63ca12299ec Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 26 Nov 2025 16:14:08 +0100 Subject: [PATCH 26/43] computing SIB1 TDA (for each SSB) only in the first iteration and stored for the rest of the run --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 107 +++++++++++------- openair2/LAYER2/NR_MAC_gNB/main.c | 3 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 1 + 3 files changed, 70 insertions(+), 41 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index 600c4b7130..dd5558d546 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -375,8 +375,10 @@ static NR_sched_pdsch_t schedule_control_sib1(gNB_MAC_INST *gNB_mac, for (int rb = 0; rb < pdsch.rbSize; rb++) { vrb_map[rb + type0_PDCCH_CSS_config->cset_start_rb] |= SL_to_bitmap(tda_info->startSymbolIndex, tda_info->nrOfSymbols); } - } else + } else { pdsch.tb_size = 0; // to signal we couldn't allocate TBS + pdsch.time_domain_allocation = -1; + } return pdsch; } @@ -472,6 +474,53 @@ static void nr_fill_nfapi_dl_SIB_pdu(gNB_MAC_INST *gNB_mac, pdcch_pdu_rel15->numDlDci); } +static bool check_sib1_tda(gNB_MAC_INST *gNB_mac, + NR_sched_pdcch_t *sched_pdcch, + NR_beam_alloc_t *beam, + int tda, + int ssb_index, + int CC_id, + int candidate_idx) +{ + NR_COMMON_channels_t *cc = &gNB_mac->common_channels[CC_id]; + NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; + NR_Type0_PDCCH_CSS_config_t *type0_PDCCH_CSS_config = &gNB_mac->type0_PDCCH_CSS_config[ssb_index]; + default_table_type_t table_type = get_default_table_type(type0_PDCCH_CSS_config->type0_pdcch_ss_mux_pattern); + // If the PDSCH was scheduled with SI-RNTI in PDCCH Type0 common search space, + // the UE may assume that this PDSCH resource allocation is not applied (Table 5.1.2.1.1-4 and 5 in 38.214) + int row = tda + 1; + if (table_type == defaultB && (row == 12 || row == 13 || row == 14)) + false; + if (table_type == defaultC && (row == 1 || row == 6 || row == 7 ||row > 12)) + false; + // assuming normal CP + NR_tda_info_t tda_info = get_info_from_tda_tables(table_type, tda, scc->dmrs_TypeA_Position, true); + // SIB1 TDA going to the last symbol would cause overlap with CSI-RS + if (!tda_info.valid_tda || tda_info.startSymbolIndex + tda_info.nrOfSymbols == 14) + false; + if (tda_info.startSymbolIndex < type0_PDCCH_CSS_config->first_symbol_index + type0_PDCCH_CSS_config->num_symbols) + false; + + NR_pdsch_dmrs_t dmrs_parms = get_dl_dmrs_params(scc, NULL, &tda_info, 1); + // Configure sched_ctrlCommon for SIB1 + gNB_mac->sib1_pdsch[ssb_index] = schedule_control_sib1(gNB_mac, + CC_id, + sched_pdcch, + type0_PDCCH_CSS_config, + tda, + &dmrs_parms, + &tda_info, + candidate_idx, + beam->idx, + cc->sib1_bcch_length); + if (gNB_mac->sib1_pdsch[ssb_index].tb_size > 0) { + LOG_D(NR_MAC, "Found TDA for SIB1: %d\n", tda); + return true; + } else { + return false; + } +} + void schedule_nr_sib1(module_id_t module_idP, frame_t frameP, slot_t slotP, @@ -483,7 +532,8 @@ void schedule_nr_sib1(module_id_t module_idP, const int CC_id = 0; uint8_t candidate_idx = 0; gNB_MAC_INST *gNB_mac = RC.nrmac[module_idP]; - NR_ServingCellConfigCommon_t *scc = gNB_mac->common_channels[CC_id].ServingCellConfigCommon; + NR_COMMON_channels_t *cc = &gNB_mac->common_channels[CC_id]; + NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon; int L_max; switch (scc->ssb_PositionsInBurst->present) { case 1: @@ -515,55 +565,32 @@ void schedule_nr_sib1(module_id_t module_idP, int beam_index = get_beam_from_ssbidx(gNB_mac, i); NR_beam_alloc_t beam = beam_allocation_procedure(&gNB_mac->beam_info, frameP, slotP, beam_index, n_slots_frame); AssertFatal(beam.idx >= 0, "Cannot allocate SIB1 corresponding to SSB %d in any available beam\n", i); - NR_COMMON_channels_t *cc = &gNB_mac->common_channels[0]; LOG_D(NR_MAC,"(%d.%d) SIB1 transmission: ssb_index %d\n", frameP, slotP, type0_PDCCH_CSS_config->ssb_index); - default_table_type_t table_type = get_default_table_type(type0_PDCCH_CSS_config->type0_pdcch_ss_mux_pattern); NR_sched_pdcch_t sched_pdcch = set_pdcch_structure(NULL, gNB_mac->sched_ctrlCommon->search_space, gNB_mac->sched_ctrlCommon->coreset, scc, NULL, type0_PDCCH_CSS_config); - int time_domain_allocation = -1; - NR_sched_pdsch_t sched_pdsch = {0}; - for (int t = 0; t < 16; t++) { - // If the PDSCH was scheduled with SI-RNTI in PDCCH Type0 common search space, - // the UE may assume that this PDSCH resource allocation is not applied (Table 5.1.2.1.1-4 and 5 in 38.214) - int row = t + 1; - if (table_type == defaultB && (row == 12 || row == 13 || row == 14)) - continue; - if (table_type == defaultC && (row == 1 || row == 6 || row == 7 ||row > 12)) - continue; - // assuming normal CP - NR_tda_info_t tda_info = get_info_from_tda_tables(table_type, t, scc->dmrs_TypeA_Position, true); - // SIB1 TDA going to the last symbol would cause overlap with CSI-RS - if (!tda_info.valid_tda || tda_info.startSymbolIndex + tda_info.nrOfSymbols == 14) - continue; - if (tda_info.startSymbolIndex < type0_PDCCH_CSS_config->first_symbol_index + type0_PDCCH_CSS_config->num_symbols) - continue; - NR_pdsch_dmrs_t dmrs_parms = get_dl_dmrs_params(scc, NULL, &tda_info, 1); - // Configure sched_ctrlCommon for SIB1 - sched_pdsch = schedule_control_sib1(gNB_mac, - CC_id, - &sched_pdcch, - type0_PDCCH_CSS_config, - t, - &dmrs_parms, - &tda_info, - candidate_idx, - beam.idx, - cc->sib1_bcch_length); - if (sched_pdsch.tb_size > 0) { - time_domain_allocation = t; - break; + bool res = false; + if (gNB_mac->sib1_pdsch[i].time_domain_allocation < 0) { + for (int t = 0; t < 16; t++) { + if (check_sib1_tda(gNB_mac, &sched_pdcch, &beam, t, i, CC_id, candidate_idx)) { + res = true; + break; + } } } - AssertFatal(time_domain_allocation > 0, "Couldn't select any TDA for SIB1\n"); + AssertFatal(gNB_mac->sib1_pdsch[i].time_domain_allocation >= 0, "Couldn't select any TDA for SIB1\n"); + if (!res) + res = check_sib1_tda(gNB_mac, &sched_pdcch, &beam, gNB_mac->sib1_pdsch[i].time_domain_allocation, i, CC_id, candidate_idx); + int tb_size = gNB_mac->sib1_pdsch[i].tb_size; + AssertFatal(res && tb_size > 0, "Couldn't allocate TB for SIB1 for an already allocated TDA\n"); nfapi_nr_dl_tti_request_body_t *dl_req = &DL_req->dl_tti_request_body; int pdu_index = gNB_mac->pdu_index[0]++; nr_fill_nfapi_dl_SIB_pdu(gNB_mac, - &sched_pdsch, + &gNB_mac->sib1_pdsch[i], &sched_pdcch, gNB_mac->sched_ctrlCommon->search_space, gNB_mac->sched_ctrlCommon->coreset, @@ -579,11 +606,11 @@ void schedule_nr_sib1(module_id_t module_idP, nfapi_nr_pdu_t *tx_req = &TX_req->pdu_list[ntx_req]; // Data to be transmitted - memcpy(tx_req->TLVs[0].value.direct, cc->sib1_bcch_pdu, sched_pdsch.tb_size); + memcpy(tx_req->TLVs[0].value.direct, cc->sib1_bcch_pdu, tb_size); tx_req->PDU_index = pdu_index; tx_req->num_TLV = 1; - tx_req->TLVs[0].length = sched_pdsch.tb_size; + tx_req->TLVs[0].length = tb_size; tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, tx_req->TLVs[0].length); TX_req->Number_of_PDUs++; TX_req->SFN = frameP; diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c index f3f070b0e7..7b32665717 100644 --- a/openair2/LAYER2/NR_MAC_gNB/main.c +++ b/openair2/LAYER2/NR_MAC_gNB/main.c @@ -302,7 +302,8 @@ void mac_top_init_gNB(ngran_node_t node_type, RC.nrmac[i]->tag = (NR_TAG_t*)malloc(sizeof(NR_TAG_t)); memset((void*)RC.nrmac[i]->tag,0,sizeof(NR_TAG_t)); - + for(int n = 0; n < MAX_NUM_OF_SSB; n++) + RC.nrmac[i]->sib1_pdsch[n].time_domain_allocation = -1; RC.nrmac[i]->common_channels[0].ServingCellConfigCommon = scc; RC.nrmac[i]->radio_config = *config; RC.nrmac[i]->rlc_config = *default_rlc_config; diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 8863098de5..9dd9276282 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -993,6 +993,7 @@ typedef struct gNB_MAC_INST_s { bool identity_pm; int precoding_matrix_size[NR_MAX_NB_LAYERS]; int beam_index_list[MAX_NUM_OF_SSB]; + NR_sched_pdsch_t sib1_pdsch[MAX_NUM_OF_SSB]; /// dedicate UL TDA, common for all UEs seq_arr_t ul_tda; From 143c545928ed78f569274f834e686caf85324130 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 1 Oct 2025 16:52:36 +0200 Subject: [PATCH 27/43] Beam simulation for rfsimulator A simplified beam switching simulation for rfsimulator. This works mainly in 1 gNB - N UEs scenarios. This is not a real beamformer with antenna, channel and propagation model. The way it works is that the peers inform each other about the beams they transmit in, and on reception before channel modelling, the beam data is combined based on preconfigured rx_beam/tx_beam gain matrix. Usage: - Add --rfsimulator.enable_beams to enable beam simulation - Add --rfsimulator.beam_gains . Example list: 0,-5,-10 Resulting matrix: [[0, -5, -10] [-5, 0, -5]] [-10, -5, 0]] - Add --telnetsrv if you want to control the beams manually. The command in telnet is "rfsimu setbeam ", where beam_map is a uint64_t controling the reception beam and transmission beam. If the application is switching the beams automatically the beams will be overwritten as soon as the application chooses to switch beams. Co-authored-by: Laurent Thomas --- radio/rfsimulator/simulator.cpp | 393 ++++++++++++++++++++++++++++---- 1 file changed, 347 insertions(+), 46 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index fe3335009d..6d78ab456a 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -51,6 +51,10 @@ extern "C" { #include } #include "rfsimulator.h" +#include +#include +#include +#include #define PORT 4043 // default TCP port for this simulator // @@ -72,6 +76,7 @@ extern "C" { #define GENERATE_CHANNEL 10 // each frame (or slot?) in DL #define MAX_FD_RFSIMU 250 #define SEND_BUFF_SIZE 100000000 // Socket buffer size +#define MAX_BEAMS 64 // Simulator role typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; @@ -80,6 +85,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; #define RFSIMU_SECTION "rfsimulator" #define RFSIMU_OPTIONS_PARAMNAME "options" +#define RFSIMU_BEAM_GAINS "beam_gains" #define RFSIM_CONFIG_HELP_OPTIONS \ " list of comma separated options to enable rf simulator functionalities. Available options: \n" \ @@ -103,14 +109,23 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; {"offset", "\n", simOpt, .u64ptr = &(rfsimulator->chan_offset), .defint64val = 0, TYPE_UINT64, 0}, \ {"prop_delay", "\n", simOpt, .dblptr=&(rfsimulator->prop_delay_ms), .defdblval=0.0, TYPE_DOUBLE, 0 },\ {"wait_timeout", "\n", simOpt, .iptr=&(rfsimulator->wait_timeout), .defintval=1, TYPE_INT, 0 },\ + {"enable_beams", "enable simplified beam simulation", PARAMFLAG_BOOL, .iptr=&(beam_ctrl->enable_beams), .defintval=0, TYPE_INT, 0},\ + {"num_concurrent_beams", "number of concurrent beams supported", simOpt, .iptr=&(beam_ctrl->num_concurrent_beams), .defintval=1, TYPE_INT, 0},\ + {"tx_beam_map", "initial beam map", simOpt, .u64ptr=&(beam_ctrl->tx.beam_map), .defintval=1, TYPE_UINT64, 0},\ + {"rx_beam_map", "initial beam map", simOpt, .u64ptr=&(beam_ctrl->rx.beam_map), .defintval=1, TYPE_UINT64, 0},\ + {RFSIMU_BEAM_GAINS, "beam gain matrix in toeplitz form (first row only)", simOpt, .strptr=NULL, .defstrval=NULL, TYPE_STRING, 0}, \ }; // clang-format on static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); -extern int get_currentchannels_type(const char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); // in random_channel.c +extern int get_currentchannels_type(const char *buf, + int debug, + webdatadef_t *tdata, + telnet_printfunc_t prnt); // in random_channel.c static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_getdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_vtime_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); +static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void *arg); // clang-format off static telnetshell_cmddef_t rfsimu_cmdarray[] = { {"show models", "", (cmdfunc_t)rfsimu_setchanmod_cmd, {(webfunc_t)getset_currentchannels_type}, TELNETSRV_CMDFLAG_WEBSRVONLY | TELNETSRV_CMDFLAG_GETWEBTBLDATA, NULL}, @@ -118,6 +133,7 @@ static telnetshell_cmddef_t rfsimu_cmdarray[] = { {"setdistance", " ", (cmdfunc_t)rfsimu_setdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_NEEDPARAM }, {"getdistance", "", (cmdfunc_t)rfsimu_getdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, {"vtime", "", (cmdfunc_t)rfsimu_vtime_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_AUTOUPDATE}, + {"setbeam", "beam_map", (cmdfunc_t)rfsimu_set_beam, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, {"", "", NULL}, }; // clang-format on @@ -127,6 +143,17 @@ static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}}; static uint64_t CirSize = minCirSize; typedef c16_t sample_t; // 2*16 bits complex number +typedef struct beam_switch_command_t { + uint64_t beam_map; + openair0_timestamp timestamp; +} beam_switch_command_t; + +typedef struct { + uint64_t beam_map; + std::queue cmd_queue; + std::mutex mutex; +} beam_state_t; + typedef struct buffer_s { int conn_sock; openair0_timestamp lastReceivedTS; @@ -138,8 +165,19 @@ typedef struct buffer_s { char *circularBufEnd; sample_t *circularBuf; channel_desc_t *channel_model; + char *beamPtr; + size_t beam_ptr_sz; + size_t remainToTransferBeam; } buffer_t; +typedef struct { + int enable_beams; + int num_concurrent_beams; + std::vector> beam_gains; + beam_state_t tx; + beam_state_t rx; +} rfsim_beam_ctrl_t; + typedef struct { int listen_sock, epollfd; pthread_mutex_t Sockmutex; @@ -165,8 +203,75 @@ typedef struct { poll_telnetcmdq_func_t poll_telnetcmdq; int wait_timeout; double prop_delay_ms; + rfsim_beam_ctrl_t *beam_ctrl; } rfsimulator_state_t; +/** + * @brief Get the current beam map for a given timestamp and number of samples. + * + * This function retrieves the beam map from the beam state, considering any queued beam switch commands. + * + * @param beam_state Pointer to the beam_state_t structure containing the current beam map and command queue. + * @param timestamp The timestamp for which the beam map is requested. + * @param nsamps The number of samples to process. + * @param nsamps_out output pointer to receive the number of samples until the next beam switch. + * @return The beam map (uint64_t) valid for the given timestamp. + */ +static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out) +{ + std::lock_guard lock(beam_state->mutex); + uint64_t current_map = beam_state->beam_map; + uint32_t samples_to_next_switch = nsamps; + + // Find the latest beam_switch_command_t with timestamp <= requested timestamp + if (!beam_state->cmd_queue.empty()) { + // Copy queue to avoid modifying original + std::queue queue_copy = beam_state->cmd_queue; + while (!queue_copy.empty()) { + const beam_switch_command_t &cmd = queue_copy.front(); + if (cmd.timestamp <= timestamp) { + current_map = cmd.beam_map; + queue_copy.pop(); + } else { + samples_to_next_switch = (cmd.timestamp > timestamp) ? (cmd.timestamp - timestamp) : nsamps; + break; + } + } + } + + // Clamp samples_to_next_switch to nsamps + if (samples_to_next_switch > nsamps) + samples_to_next_switch = nsamps; + + *nsamps_out = samples_to_next_switch; + + return current_map; +} + +/** + * @brief Clears outdated beam switch commands from the queue and updates the current beam map. + * + * This function processes the beam switch command queue in the given beam_state_t. + * For each command with a timestamp less than or equal to the provided timestamp, + * it updates the current beam_map and removes the command from the queue. + * The queue is left unchanged for commands with timestamps greater than the provided timestamp. + * + * @param beam_state Pointer to the beam_state_t structure containing the command queue and current beam map. + * @param timestamp The timestamp up to which commands should be processed and removed. + */ +static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timestamp) +{ + std::lock_guard lock(beam_state->mutex); + while (!beam_state->cmd_queue.empty()) { + if (beam_state->cmd_queue.front().timestamp <= timestamp) { + beam_state->beam_map = beam_state->cmd_queue.front().beam_map; + beam_state->cmd_queue.pop(); + } else { + break; + } + } +} + static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time); static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock) @@ -252,10 +357,7 @@ static void socketError(rfsimulator_state_t *bridge, buffer_t *buf) } } -enum blocking_t { - notBlocking, - blocking -}; +enum blocking_t { notBlocking, blocking }; static int setblocking(int sock, enum blocking_t active) { @@ -278,7 +380,8 @@ static int setblocking(int sock, enum blocking_t active) return 0; } -static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) { +static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) +{ if (t->saveIQfile != -1) { if (write(t->saveIQfile, _buf, count) != count) LOG_E(HW, "write() in save iq file failed (%d)\n", errno); @@ -314,9 +417,57 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t) } } -static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { +static float get_rx_gain_db(rfsimulator_state_t *rfsimulator, uint rx_beam, uint tx_beam) +{ + if (!rfsimulator->beam_ctrl->enable_beams) { + return 0; + } + AssertFatal(rx_beam < rfsimulator->beam_ctrl->beam_gains.size() && tx_beam < rfsimulator->beam_ctrl->beam_gains[rx_beam].size(), + "Beam gain for this combination was not provided rx_beam %d tx_beam %d\n", + rx_beam, + tx_beam); + return rfsimulator->beam_ctrl->beam_gains[rx_beam][tx_beam]; +} + +static int rfsimulator_set_beams(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp) +{ + rfsimulator_state_t *s = static_cast(device->priv); + rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl; + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard lock_rx(beam_ctrl->rx.mutex); + beam_switch_command_t command = {.beam_map = beam_map, .timestamp = timestamp}; + beam_ctrl->rx.cmd_queue.emplace(command); + beam_ctrl->tx.cmd_queue.emplace(command); + return 0; +} + +static void process_gains(char *str, rfsim_beam_ctrl_t *beam_ctrl) +{ + int num_gains = 0; + float gain_array[MAX_BEAMS]; + std::stringstream ss(str); + std::string token; + while (std::getline(ss, token, ',') && num_gains < MAX_BEAMS) { + gain_array[num_gains++] = std::stof(token); + } + + if (num_gains != 0) { + for (int i = 0; i < num_gains; i++) { + std::vector beam_gains; + for (int j = 0; j < num_gains; j++) { + int diag = abs(i - j); + beam_gains.push_back(gain_array[diag]); + } + beam_ctrl->beam_gains.push_back(beam_gains); + } + } +} + +static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) +{ char *saveF = NULL; char *modelname = NULL; + rfsim_beam_ctrl_t *beam_ctrl = rfsimulator->beam_ctrl; paramdef_t rfsimu_params[] = RFSIMULATOR_PARAMS_DESC; int p = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_OPTIONS_PARAMNAME); int ret = config_get(config_get_if(), rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_SECTION); @@ -338,7 +489,11 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { break; } else if (strcmp(rfsimu_params[p].strlistptr[i], "chanmod") == 0) { init_channelmod(); - load_channellist(rfsimulator->tx_num_channels, rfsimulator->rx_num_channels, rfsimulator->sample_rate, rfsimulator->rx_freq, rfsimulator->tx_bw); + load_channellist(rfsimulator->tx_num_channels, + rfsimulator->rx_num_channels, + rfsimulator->sample_rate, + rfsimulator->rx_freq, + rfsimulator->tx_bw); rfsimulator->channelmod = true; } else { fprintf(stderr, "unknown rfsimulator option: %s\n", rfsimu_params[p].strlistptr[i]); @@ -346,14 +501,33 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) { } } - if ( strncasecmp(rfsimulator->ip,"enb",3) == 0 || - strncasecmp(rfsimulator->ip,"server",3) == 0 ) + int beam_gains_param_index = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_BEAM_GAINS); + if (rfsimu_params[beam_gains_param_index].strptr) { + process_gains(*rfsimu_params[beam_gains_param_index].strptr, beam_ctrl); + } + + if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0) rfsimulator->role = SIMU_ROLE_SERVER; else rfsimulator->role = SIMU_ROLE_CLIENT; } -static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg) { +static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void *arg) +{ + rfsimulator_state_t *t = (rfsimulator_state_t *)arg; + rfsim_beam_ctrl_t *beam_ctrl = t->beam_ctrl; + AssertFatal(beam_ctrl->enable_beams, "Beam simualtion is disabled, cannot set beams\n"); + uint64_t beam_map = strtoull(buff, NULL, 0); + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard lock_rx(beam_ctrl->rx.mutex); + beam_ctrl->rx.beam_map = beam_map; + beam_ctrl->tx.beam_map = beam_map; + prnt("Beam map set to 0x%lx\n", beam_map); + return CMDSTATUS_FOUND; +} + +static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg) +{ char *modelname = NULL; char *modeltype = NULL; rfsimulator_state_t *t = (rfsimulator_state_t *)arg; @@ -461,7 +635,8 @@ static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt /* Set distance in rfsim and channel model, update channel and ringbuffer */ for (int i = 0; i < MAX_FD_RFSIMU; i++) { buffer_t *b = &t->buf[i]; - if (b->conn_sock <= 0 || b->channel_model == NULL || b->channel_model->model_name == NULL || strcmp(b->channel_model->model_name, modelname) != 0) { + if (b->conn_sock <= 0 || b->channel_model == NULL || b->channel_model->model_name == NULL + || strcmp(b->channel_model->model_name, modelname) != 0) { if (b->channel_model != NULL && b->channel_model->model_name != NULL) prnt(" %s: model %s unmodified\n", __func__, b->channel_model->model_name); continue; @@ -522,9 +697,9 @@ static int startServer(openair0_device *device) snprintf(port, sizeof(port), "%d", t->port); struct addrinfo hints = { - .ai_flags = AI_PASSIVE, - .ai_family = AF_INET6, - .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE, + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, }; int s = getaddrinfo(NULL, port, &hints, &results); @@ -680,16 +855,30 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, LOG_E(HW, "rfsimulator sending 0 tx antennas\n"); samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beam_map}; fullwrite(b->conn_sock, &header, sizeof(header), t); - if (nbAnt == 1) { - fullwrite(b->conn_sock, samplesVoid[0][0], sampleToByte(nsamps, nbAnt), t); + if (t->beam_ctrl->enable_beams) { + int num_beams = __builtin_popcountll(beam_map); + AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n"); + sample_t tmpSamples[nsamps][num_beams][nbAnt]; + for (int beam = 0; beam < num_beams; beam++) { + for (int a = 0; a < nbAnt; a++) { + sample_t *in = (sample_t *)samplesVoid[beam][a]; + for (int s = 0; s < nsamps; s++) + tmpSamples[s][beam][a] = in[s]; + } + } + fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt * num_beams), t); } else { - sample_t tmpSamples[nsamps][nbAnt]; - for (int a = 0; a < nbAnt; a++) { - sample_t *in = (sample_t *)samplesVoid[0][a]; - for (int s = 0; s < nsamps; s++) - tmpSamples[s][a] = in[s]; + if (nbAnt == 1) { + fullwrite(b->conn_sock, samplesVoid[0][0], sampleToByte(nsamps, nbAnt), t); + } else { + sample_t tmpSamples[nsamps][nbAnt]; + for (int a = 0; a < nbAnt; a++) { + sample_t *in = (sample_t *)samplesVoid[0][a]; + for (int s = 0; s < nsamps; s++) + tmpSamples[s][a] = in[s]; + } + fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt), t); } - fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt), t); } } } @@ -732,8 +921,31 @@ static int rfsimulator_write_beams(openair0_device *device, int flags) { timestamp -= device->openair0_cfg->command_line_sample_advance; + int nsamps_initial = nsamps; rfsimulator_state_t *t = static_cast(device->priv); - return rfsimulator_write_internal(t, timestamp, samplesVoid, nsamps, nbAnt, 1, flags); + void *samples[num_beams][nbAnt]; + void **samples_ptr[num_beams]; + for (int beam = 0; beam < num_beams; beam++) { + samples_ptr[beam] = samples[beam]; + for (int aatx = 0; aatx < nbAnt; aatx++) { + samples[beam][aatx] = samplesVoid[beam][aatx]; + } + } + while (nsamps > 0) { + uint32_t nsamps_beam_map; + uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map); + rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beam_map, flags); + for (int beam = 0; beam < num_beams; beam++) { + for (int aatx = 0; aatx < nbAnt; aatx++) { + char *ptr = (char *)samples_ptr[beam][aatx]; + samples_ptr[beam][aatx] = (void *)(ptr + nsamps_beam_map * sizeof(sample_t)); + } + } + timestamp += nsamps_beam_map; + nsamps -= nsamps_beam_map; + } + clear_beam_queue(&t->beam_ctrl->tx, timestamp + nsamps); + return nsamps_initial; } static int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int cc, int flags) @@ -823,8 +1035,19 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ b->transferPtr = (char *)&b->circularBuf[(b->lastReceivedTS * b->th.nbAnt) % CirSize]; // we now need to read the samples b->remainToTransfer = sampleToByte(b->th.size, b->th.nbAnt); - if (!b->remainToTransfer) - b->headerMode = true; // We got a header with 0 antennas, no I/Q to read + + if (t->beam_ctrl->enable_beams) { + int num_beams = __builtin_popcountll(b->th.beam_map); + AssertFatal(num_beams > 0, "Needs at least one beam\n"); + int num_ant = b->th.nbAnt; + int num_samples = b->th.size; + size_t beam_packet_size = sizeof(sample_t) * num_ant * num_beams * num_samples; + if (b->beamPtr == NULL || beam_packet_size > b->beam_ptr_sz) { + free(b->beamPtr); + b->beamPtr = static_cast(malloc(beam_packet_size)); + } + b->remainToTransferBeam = beam_packet_size; + } return; } @@ -883,15 +1106,83 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) ssize_t blockSz; if (b->headerMode) blockSz = b->remainToTransfer; - else + else { blockSz = b->transferPtr + b->remainToTransfer <= b->circularBufEnd ? b->remainToTransfer : b->circularBufEnd - b->transferPtr; + } - ssize_t sz = recv(b->conn_sock, b->transferPtr, blockSz, MSG_DONTWAIT); - if (sz <= 0) { - if (sz < 0 && errno != EAGAIN) - LOG_E(HW, "recv() failed, errno(%d)\n", errno); - continue; + ssize_t sz = 0; + if (!b->headerMode && t->beam_ctrl->enable_beams) { + int num_beams = __builtin_popcountll(b->th.beam_map); + AssertFatal(num_beams > 0, "Needs at least one beam\n"); + int num_ant = b->th.nbAnt; + int num_samples = b->th.size; + size_t beam_packet_size = sizeof(sample_t) * num_beams * num_ant * num_samples; + size_t bytes_received = beam_packet_size - b->remainToTransferBeam; + ssize_t recv_size = recv(b->conn_sock, &b->beamPtr[bytes_received], b->remainToTransferBeam, MSG_DONTWAIT); + if (recv_size <= 0) { + if (recv_size < 0 && errno != EAGAIN) + LOG_E(HW, "recv() failed, errno(%d)\n", errno); + continue; + } + b->remainToTransferBeam -= recv_size; + + int beam_sample_sz = num_beams * num_ant * sizeof(sample_t); + int previously_written_sample = bytes_received / beam_sample_sz; + int last_full_sample = (bytes_received + recv_size) / beam_sample_sz; + int num_samples_received = last_full_sample - previously_written_sample; + int first_sample = previously_written_sample + 1; + openair0_timestamp sample_timestamp = b->th.timestamp + first_sample; + + int tx_beam_ids[MAX_BEAMS]; + int *tx_beam_ids_p = tx_beam_ids; + for (int i = 0; i < MAX_BEAMS; i++) { + if (b->th.beam_map & (1ULL << i)) { + *tx_beam_ids_p++ = i; + } + } + c16_t *in = (c16_t *)&b->beamPtr[first_sample * beam_sample_sz]; + int num_samples_to_process = num_samples_received; + openair0_timestamp first_sample_timestamp = sample_timestamp; + while (num_samples_to_process > 0) { + uint32_t nsamps_out; + uint64_t rx_beam_map = get_beam_map(&t->beam_ctrl->rx, first_sample_timestamp, num_samples_to_process, &nsamps_out); + AssertFatal(__builtin_popcountll(rx_beam_map) == 1, "Only supports 1 beam\n"); + int rx_beam_id = 0; + for (int i = 0; i < MAX_BEAMS; i++) { + if (rx_beam_map & (1ULL << i)) { + rx_beam_id = i; + break; + } + } + for (uint i = 0; i < nsamps_out; i++) { + for (int aatx = 0; aatx < num_ant; aatx++) { + c16_t *out = &b->circularBuf[((first_sample_timestamp + i) * num_ant + aatx) % CirSize]; + cf_t sample = {.r = 0, .i = 0}; + for (int beam = 0; beam < num_beams; beam++) { + float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beam_ids[beam]); + float gain_linear = powf(10, gain_dB / 20.0); + sample.r += in->r * gain_linear; + sample.i += in->i * gain_linear; + in++; + } + out->r = sample.r; + out->i = sample.i; + } + } + first_sample_timestamp += nsamps_out; + num_samples_to_process -= nsamps_out; + } + int antenna_samples_sz = num_ant * sizeof(sample_t); + // pretend we received some samples without beams + sz = num_samples_received * antenna_samples_sz; + } else { + sz = recv(b->conn_sock, b->transferPtr, blockSz, MSG_DONTWAIT); + if (sz <= 0) { + if (sz < 0 && errno != EAGAIN) + LOG_E(HW, "recv() failed, errno(%d)\n", errno); + continue; + } } LOG_D(HW, "Socket rcv %zd bytes\n", sz); b->remainToTransfer -= sz; @@ -946,8 +1237,8 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { b = &t->buf[sock]; if (b->circularBuf && (t->nextRxTstamp + nsamps) > b->lastReceivedTS) { - have_to_wait = true; - break; + have_to_wait = true; + break; } } @@ -965,6 +1256,9 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest int ret = clock_gettime(CLOCK_REALTIME, &start_time); AssertFatal(ret == 0, "clock_gettime() failed: errno %d, %s\n", errno, strerror(errno)); + if (t->poll_telnetcmdq) + t->poll_telnetcmdq(t->telnetcmd_qid, t); + // Clear the output buffer for (int a = 0; a < nbAnt; a++) memset(samplesVoid[a], 0, sampleToByte(nsamps, 1)); @@ -983,9 +1277,6 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest if (reGenerateChannel) random_channel(ptr->channel_model, 0); - if (t->poll_telnetcmdq) - t->poll_telnetcmdq(t->telnetcmd_qid, t); - for (int a_rx = 0; a_rx < nbAnt; a_rx++) { // loop over number of Rx antennas if (ptr->channel_model != NULL) { // apply a channel model if (num_chanmod_channels == 0) { @@ -1066,6 +1357,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest *ptimestamp = t->nextRxTstamp; // return the time of the first sample t->nextRxTstamp += nsamps; + clear_beam_queue(&t->beam_ctrl->rx, t->nextRxTstamp); LOG_D(HW, "Rx to upper layer: %d from %ld to %ld, energy in first antenna %d\n", nsamps, @@ -1079,13 +1371,16 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest return nsamps; } -static int rfsimulator_get_stats(openair0_device *device) { +static int rfsimulator_get_stats(openair0_device *device) +{ return 0; } -static int rfsimulator_reset_stats(openair0_device *device) { +static int rfsimulator_reset_stats(openair0_device *device) +{ return 0; } -static void rfsimulator_end(openair0_device *device) { +static void rfsimulator_end(openair0_device *device) +{ rfsimulator_state_t *s = static_cast(device->priv); for (int i = 0; i < MAX_FD_RFSIMU; i++) { buffer_t *b = &s->buf[i]; @@ -1103,23 +1398,27 @@ static void stopServer(openair0_device *device) rfsimulator_end(device); } -static int rfsimulator_stop(openair0_device *device) { +static int rfsimulator_stop(openair0_device *device) +{ return 0; } -static int rfsimulator_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) { +static int rfsimulator_set_freq(openair0_device *device, openair0_config_t *openair0_cfg) +{ rfsimulator_state_t *s = static_cast(device->priv); s->rx_freq = openair0_cfg->rx_freq[0]; return 0; } -static int rfsimulator_set_gains(openair0_device *device, openair0_config_t *openair0_cfg) { +static int rfsimulator_set_gains(openair0_device *device, openair0_config_t *openair0_cfg) +{ return 0; } -static int rfsimulator_write_init(openair0_device *device) { +static int rfsimulator_write_init(openair0_device *device) +{ return 0; } -extern "C" __attribute__((__visibility__("default"))) -int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { +extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) +{ // to change the log level, use this on command line // --log_config.hw_log_level debug rfsimulator_state_t *rfsimulator = static_cast(calloc(sizeof(rfsimulator_state_t), 1)); @@ -1129,6 +1428,7 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { rfsimulator->sample_rate = openair0_cfg->sample_rate; rfsimulator->rx_freq = openair0_cfg->rx_freq[0]; rfsimulator->tx_bw = openair0_cfg->tx_bw; + rfsimulator->beam_ctrl = new rfsim_beam_ctrl_t; rfsimulator_readconfig(rfsimulator); if (rfsimulator->prop_delay_ms > 0.0) rfsimulator->chan_offset = ceil(rfsimulator->sample_rate * rfsimulator->prop_delay_ms / 1000); @@ -1161,6 +1461,7 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { device->openair0_cfg = &openair0_cfg[0]; device->priv = rfsimulator; device->trx_write_init = rfsimulator_write_init; + device->trx_set_beams = rfsimulator_set_beams; for (int i = 0; i < MAX_FD_RFSIMU; i++) rfsimulator->buf[i].conn_sock = -1; From f0d4325cd7e1c6141d07c7b06e4186e7d017cda2 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 13 Oct 2025 15:02:30 +0200 Subject: [PATCH 28/43] rfsim: process received packet when its received completely The simplifies some code in packet reception and is a prerequisite for processing received without relying on circular buffer --- radio/rfsimulator/simulator.cpp | 194 +++++++++++++------------------- 1 file changed, 76 insertions(+), 118 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 6d78ab456a..983f9e4c39 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,7 @@ * When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic */ +#include "utils.h" #include #include #include @@ -165,8 +166,8 @@ typedef struct buffer_s { char *circularBufEnd; sample_t *circularBuf; channel_desc_t *channel_model; - char *beamPtr; - size_t beam_ptr_sz; + char *packet_ptr; + size_t payload_sz; size_t remainToTransferBeam; } buffer_t; @@ -993,10 +994,6 @@ static bool add_client(rfsimulator_state_t *t) static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_time) { - // check the header and start block transfer - if (b->remainToTransfer != 0) - return; - b->headerMode = false; // We got the header if (first_time) { @@ -1031,42 +1028,65 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ LOG_W(HW, "UEsock(%d) Tx/Rx shift too large Tx:%lu, Rx:%lu\n", b->conn_sock, t->lastWroteTS, b->lastReceivedTS); mutexunlock(t->Sockmutex); } - // move back the pointer to overwrite the header, we don't need it anymore - b->transferPtr = (char *)&b->circularBuf[(b->lastReceivedTS * b->th.nbAnt) % CirSize]; - // we now need to read the samples - b->remainToTransfer = sampleToByte(b->th.size, b->th.nbAnt); - if (t->beam_ctrl->enable_beams) { - int num_beams = __builtin_popcountll(b->th.beam_map); - AssertFatal(num_beams > 0, "Needs at least one beam\n"); - int num_ant = b->th.nbAnt; - int num_samples = b->th.size; - size_t beam_packet_size = sizeof(sample_t) * num_ant * num_beams * num_samples; - if (b->beamPtr == NULL || beam_packet_size > b->beam_ptr_sz) { - free(b->beamPtr); - b->beamPtr = static_cast(malloc(beam_packet_size)); - } - b->remainToTransferBeam = beam_packet_size; + int num_beams = __builtin_popcountll(b->th.beam_map); + AssertFatal(b->th.beam_map == 1ULL || t->beam_ctrl->enable_beams == 1, + "The transmitter has enabled beam simulation while this receiver has not\n"); + size_t payload_sz = sampleToByte(b->th.size, b->th.nbAnt) * num_beams; + if (b->packet_ptr == NULL || payload_sz > b->payload_sz) { + free(b->packet_ptr); + b->packet_ptr = static_cast(calloc_or_fail(1, payload_sz)); + b->payload_sz = payload_sz; } + b->transferPtr = b->packet_ptr; + b->remainToTransfer = payload_sz; return; } static void process_recv(rfsimulator_state_t *t, buffer_t *b, bool first_time) { - if (b->transferPtr == b->circularBufEnd) - b->transferPtr = (char *)b->circularBuf; - - if (!b->trashingPacket) { - b->lastReceivedTS = b->th.timestamp + b->th.size - byteToSample(b->remainToTransfer, b->th.nbAnt); - LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); + int num_beams = __builtin_popcountll(b->th.beam_map); + AssertFatal(num_beams > 0, "Needs at least one beam\n"); + int num_ant = b->th.nbAnt; + int num_samples = b->th.size; + int tx_beam_ids[MAX_BEAMS]; + int *tx_beam_ids_p = tx_beam_ids; + for (int i = 0; i < MAX_BEAMS; i++) { + if (b->th.beam_map & (1ULL << i)) { + *tx_beam_ids_p++ = i; + } } - - if (b->remainToTransfer == 0) { - LOG_D(HW, "UEsock: %d Completed block reception: %ld\n", b->conn_sock, b->lastReceivedTS); - b->headerMode = true; - b->transferPtr = (char *)&b->th; - b->remainToTransfer = sizeof(samplesBlockHeader_t); - b->trashingPacket = false; + c16_t *in = (c16_t *)b->packet_ptr; + int num_samples_to_process = num_samples; + openair0_timestamp first_sample_timestamp = b->th.timestamp; + while (num_samples_to_process > 0) { + uint32_t nsamps_out; + uint64_t rx_beam_map = get_beam_map(&t->beam_ctrl->rx, first_sample_timestamp, num_samples_to_process, &nsamps_out); + AssertFatal(__builtin_popcountll(rx_beam_map) == 1, "Only supports 1 beam\n"); + int rx_beam_id = 0; + for (int i = 0; i < MAX_BEAMS; i++) { + if (rx_beam_map & (1ULL << i)) { + rx_beam_id = i; + break; + } + } + for (uint i = 0; i < nsamps_out; i++) { + for (int aatx = 0; aatx < num_ant; aatx++) { + c16_t *out = &b->circularBuf[((first_sample_timestamp + i) * num_ant + aatx) % CirSize]; + cf_t sample = {.r = 0, .i = 0}; + for (int beam = 0; beam < num_beams; beam++) { + float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beam_ids[beam]); + float gain_linear = powf(10, gain_dB / 20.0); + sample.r += in->r * gain_linear; + sample.i += in->i * gain_linear; + in++; + } + out->r = sample.r; + out->i = sample.i; + } + } + first_sample_timestamp += nsamps_out; + num_samples_to_process -= nsamps_out; } } @@ -1103,94 +1123,32 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) continue; } - ssize_t blockSz; - if (b->headerMode) - blockSz = b->remainToTransfer; - else { - blockSz = - b->transferPtr + b->remainToTransfer <= b->circularBufEnd ? b->remainToTransfer : b->circularBufEnd - b->transferPtr; - } - - ssize_t sz = 0; - if (!b->headerMode && t->beam_ctrl->enable_beams) { - int num_beams = __builtin_popcountll(b->th.beam_map); - AssertFatal(num_beams > 0, "Needs at least one beam\n"); - int num_ant = b->th.nbAnt; - int num_samples = b->th.size; - size_t beam_packet_size = sizeof(sample_t) * num_beams * num_ant * num_samples; - size_t bytes_received = beam_packet_size - b->remainToTransferBeam; - ssize_t recv_size = recv(b->conn_sock, &b->beamPtr[bytes_received], b->remainToTransferBeam, MSG_DONTWAIT); - if (recv_size <= 0) { - if (recv_size < 0 && errno != EAGAIN) - LOG_E(HW, "recv() failed, errno(%d)\n", errno); - continue; - } - b->remainToTransferBeam -= recv_size; - - int beam_sample_sz = num_beams * num_ant * sizeof(sample_t); - int previously_written_sample = bytes_received / beam_sample_sz; - int last_full_sample = (bytes_received + recv_size) / beam_sample_sz; - int num_samples_received = last_full_sample - previously_written_sample; - int first_sample = previously_written_sample + 1; - openair0_timestamp sample_timestamp = b->th.timestamp + first_sample; - - int tx_beam_ids[MAX_BEAMS]; - int *tx_beam_ids_p = tx_beam_ids; - for (int i = 0; i < MAX_BEAMS; i++) { - if (b->th.beam_map & (1ULL << i)) { - *tx_beam_ids_p++ = i; - } - } - c16_t *in = (c16_t *)&b->beamPtr[first_sample * beam_sample_sz]; - int num_samples_to_process = num_samples_received; - openair0_timestamp first_sample_timestamp = sample_timestamp; - while (num_samples_to_process > 0) { - uint32_t nsamps_out; - uint64_t rx_beam_map = get_beam_map(&t->beam_ctrl->rx, first_sample_timestamp, num_samples_to_process, &nsamps_out); - AssertFatal(__builtin_popcountll(rx_beam_map) == 1, "Only supports 1 beam\n"); - int rx_beam_id = 0; - for (int i = 0; i < MAX_BEAMS; i++) { - if (rx_beam_map & (1ULL << i)) { - rx_beam_id = i; - break; - } - } - for (uint i = 0; i < nsamps_out; i++) { - for (int aatx = 0; aatx < num_ant; aatx++) { - c16_t *out = &b->circularBuf[((first_sample_timestamp + i) * num_ant + aatx) % CirSize]; - cf_t sample = {.r = 0, .i = 0}; - for (int beam = 0; beam < num_beams; beam++) { - float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beam_ids[beam]); - float gain_linear = powf(10, gain_dB / 20.0); - sample.r += in->r * gain_linear; - sample.i += in->i * gain_linear; - in++; - } - out->r = sample.r; - out->i = sample.i; - } - } - first_sample_timestamp += nsamps_out; - num_samples_to_process -= nsamps_out; - } - int antenna_samples_sz = num_ant * sizeof(sample_t); - // pretend we received some samples without beams - sz = num_samples_received * antenna_samples_sz; - } else { - sz = recv(b->conn_sock, b->transferPtr, blockSz, MSG_DONTWAIT); - if (sz <= 0) { - if (sz < 0 && errno != EAGAIN) - LOG_E(HW, "recv() failed, errno(%d)\n", errno); - continue; - } + ssize_t sz = recv(b->conn_sock, b->transferPtr, b->remainToTransfer, MSG_DONTWAIT); + if (sz <= 0) { + if (sz < 0 && errno != EAGAIN) + LOG_E(HW, "recv() failed, errno(%d)\n", errno); + continue; } LOG_D(HW, "Socket rcv %zd bytes\n", sz); b->remainToTransfer -= sz; b->transferPtr += sz; - if (b->headerMode) - process_recv_header(t, b, first_time); - else - process_recv(t, b, first_time); + if (b->remainToTransfer == 0) { + if (b->headerMode) + process_recv_header(t, b, first_time); + else { + LOG_D(HW, "UEsock: %d Completed block reception: %ld\n", b->conn_sock, b->lastReceivedTS); + b->headerMode = true; + b->transferPtr = (char *)&b->th; + b->remainToTransfer = sizeof(samplesBlockHeader_t); + + if (!b->trashingPacket) { + process_recv(t, b, first_time); + b->lastReceivedTS = b->th.timestamp + b->th.size; + LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); + } + b->trashingPacket = false; + } + } } return nfds > 0; } From 4ec69cad1ffb9e81cf3b6752129a204ffc78c3ee Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 13 Oct 2025 17:18:01 +0200 Subject: [PATCH 29/43] Fix formatting for apply_channelmod.c --- radio/rfsimulator/apply_channelmod.c | 108 ++++++++++++++++----------- radio/rfsimulator/rfsimulator.h | 61 ++++++++------- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index aca8a6b030..481c089796 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -1,26 +1,25 @@ /* -* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The OpenAirInterface Software Alliance licenses this file to You under -* the OAI Public License, Version 1.1 (the "License"); you may not use this file -* except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.openairinterface.org/?page_id=698 -* -* Author and copyright: Laurent Thomas, open-cells.com -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*------------------------------------------------------------------------------- -* For more information about the OpenAirInterface (OAI) Software Alliance: -* contact@openairinterface.org -*/ - + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Author and copyright: Laurent Thomas, open-cells.com + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ #include #include @@ -53,11 +52,14 @@ void rxAddInput(const c16_t *input_sig, { static uint64_t last_TS = 0; - if ((channelDesc->sat_height > 0) && (channelDesc->enable_dynamic_delay || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit + if ((channelDesc->sat_height > 0) + && (channelDesc->enable_dynamic_delay + || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit /* assumptions: - The Earth is spherical, the ground station is static, and that the Earth does not rotate. - An access or link is possible from the satellite to the ground station at all times. - - The ground station is located at the North Pole (positive Zaxis), and the satellite starts from the initial elevation angle 0° in the second quadrant of the YZplane. + - The ground station is located at the North Pole (positive Zaxis), and the satellite starts from the initial elevation angle + 0° in the second quadrant of the YZplane. - Satellite moves in the clockwise direction in its circular orbit. */ const double radius_earth = 6377900; // m @@ -78,7 +80,7 @@ void rxAddInput(const c16_t *input_sig, const double pos_sat_z = radius_sat * cos(w_sat * t); const double vel_sat_x = 0; - const double vel_sat_y = w_sat * radius_sat * cos(w_sat * t); + const double vel_sat_y = w_sat * radius_sat * cos(w_sat * t); const double vel_sat_z = -w_sat * radius_sat * sin(w_sat * t); const double pos_ue_x = 0; @@ -157,9 +159,17 @@ void rxAddInput(const c16_t *input_sig, if (channelDesc->enable_dynamic_Doppler) channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_ue_sat / channelDesc->sampling_rate; - if(TS - last_TS >= channelDesc->sampling_rate) { + if (TS - last_TS >= channelDesc->sampling_rate) { last_TS = TS; - LOG_I(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_I(HW, + "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", + t, + pos_sat_x, + pos_sat_y, + pos_sat_z, + vel_sat_x, + vel_sat_y, + vel_sat_z); LOG_I(HW, "Uplink delay %f ms, Doppler shift UE->SAT %f kHz\n", prop_delay * 1000, f_Doppler_shift_ue_sat / 1000); LOG_I(HW, "Satellite velocity towards gNB: %f m/s, acceleration towards gNB: %f m/s²\n", -vel_sat_gnb, acc_sat_gnb); } @@ -216,9 +226,17 @@ void rxAddInput(const c16_t *input_sig, if (channelDesc->enable_dynamic_Doppler) channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_sat_ue / channelDesc->sampling_rate; - if(TS - last_TS >= channelDesc->sampling_rate) { + if (TS - last_TS >= channelDesc->sampling_rate) { last_TS = TS; - LOG_I(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_I(HW, + "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", + t, + pos_sat_x, + pos_sat_y, + pos_sat_z, + vel_sat_x, + vel_sat_y, + vel_sat_z); LOG_I(HW, "Downlink delay %f ms, Doppler shift SAT->UE %f kHz\n", prop_delay * 1000, f_Doppler_shift_sat_ue / 1000); } } @@ -228,24 +246,24 @@ void rxAddInput(const c16_t *input_sig, // so, in actual RF: tx gain + path loss + rx gain (+antenna gain, ...) // UE and NB gain control to be added // Fixme: not sure when it is "volts" so dB is 20*log10(...) or "power", so dB is 10*log10(...) - const double pathLossLinear = pow(10,channelDesc->path_loss_dB/20.0); + const double pathLossLinear = pow(10, channelDesc->path_loss_dB / 20.0); // Energy in one sample to calibrate input noise // the normalized OAI value seems to be 256 as average amplitude (numerical amplification = 1) - const double noise_per_sample = add_noise ? pow(10,channelDesc->noise_power_dB/10.0) * 256 : 0; + const double noise_per_sample = add_noise ? pow(10, channelDesc->noise_power_dB / 10.0) * 256 : 0; const uint64_t dd = channelDesc->channel_offset; - const int nbTx=channelDesc->nb_tx; + const int nbTx = channelDesc->nb_tx; double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt]; Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); - for (int i=0; ich[rxAnt+(txAnt*channelDesc->nb_rx)]; + for (int txAnt = 0; txAnt < nbTx; txAnt++) { + const struct complexd *channelModel = channelDesc->ch[rxAnt + (txAnt * channelDesc->nb_rx)]; - //const struct complex *channelModelEnd=channelModel+channelDesc->channel_length; - for (int l = 0; l<(int)channelDesc->channel_length; l++) { + // const struct complex *channelModelEnd=channelModel+channelDesc->channel_length; + for (int l = 0; l < (int)channelDesc->channel_length; l++) { // let's assume TS+i >= l // fixme: the rfsimulator current structure is interleaved antennas // this has been designed to not have to wait a full block transmission @@ -257,7 +275,7 @@ void rxAddInput(const c16_t *input_sig, const struct complex16 tx16 = input_sig[idx]; rx_tmp.r += tx16.r * channelModel[l].r - tx16.i * channelModel[l].i; rx_tmp.i += tx16.i * channelModel[l].r + tx16.r * channelModel[l].i; - } //l + } // l } if (channelDesc->Doppler_phase_inc != 0.0) { @@ -279,12 +297,12 @@ void rxAddInput(const c16_t *input_sig, channelDesc->Doppler_phase_cur[rxAnt] = Doppler_phase_cur; - if ( (TS*nbTx)%CirSize+nbSamples <= CirSize ) + if ((TS * nbTx) % CirSize + nbSamples <= CirSize) // Cast to a wrong type for compatibility ! - LOG_D(HW,"Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", - 10*log10((double)signal_energy((int32_t *)&input_sig[(TS*nbTx)%CirSize], nbSamples)), - 10*log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), + LOG_D(HW, + "Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", + 10 * log10((double)signal_energy((int32_t *)&input_sig[(TS * nbTx) % CirSize], nbSamples)), + 10 * log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), channelDesc->path_loss_dB, - 10*log10(noise_per_sample)); + 10 * log10(noise_per_sample)); } - diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 11a57193ca..7410d10cbb 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -1,37 +1,36 @@ /* -* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The OpenAirInterface Software Alliance licenses this file to You under -* the OAI Public License, Version 1.1 (the "License"); you may not use this file -* except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.openairinterface.org/?page_id=698 -* -* Author and copyright: Laurent Thomas, open-cells.com -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*------------------------------------------------------------------------------- -* For more information about the OpenAirInterface (OAI) Software Alliance: -* contact@openairinterface.org -*/ + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Author and copyright: Laurent Thomas, open-cells.com + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ #ifndef __RFSIMULATOR_H -#define __RFSIMULATOR_H +#define __RFSIMULATOR_H #include -void rxAddInput( const c16_t *input_sig, - cf_t *after_channel_sig, - int rxAnt, - channel_desc_t *channelDesc, - int nbSamples, - uint64_t TS, - uint32_t CirSize, - bool apply_noise - ); +void rxAddInput(const c16_t *input_sig, + cf_t *after_channel_sig, + int rxAnt, + channel_desc_t *channelDesc, + int nbSamples, + uint64_t TS, + uint32_t CirSize, + bool apply_noise); #endif From 669be5b42e73dae5195cb9d941529ecf9975e6d8 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Oct 2025 08:14:51 +0200 Subject: [PATCH 30/43] Rfsim: Process received packets on demand - Received packets are dynamically allocated and kept in a queue for as long as they are needed. - The layout of the packets was changed from interleaved antennas to non-interleaved antennas. - Generate a contiguous buffer from saved packets as needed. There is extra work caused by this change due to recv() call no longer saving data directly into the circular buffer, however further processing is easier due to contiguous nature of the tx antenna buffers prepared. The impact on performance was not measured. --- radio/rfsimulator/apply_channelmod.c | 35 +-- radio/rfsimulator/rfsimulator.h | 6 +- radio/rfsimulator/simulator.cpp | 366 ++++++++++++++------------- 3 files changed, 199 insertions(+), 208 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 481c089796..2eaa6a66c2 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -41,14 +41,12 @@ either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes legacy: we regenerate each sub frame in UL, and each frame only in DL */ -void rxAddInput(const c16_t *input_sig, +void rxAddInput(const c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, int nbSamples, - uint64_t TS, - uint32_t CirSize, - bool add_noise) + uint64_t TS) { static uint64_t last_TS = 0; @@ -249,8 +247,7 @@ void rxAddInput(const c16_t *input_sig, const double pathLossLinear = pow(10, channelDesc->path_loss_dB / 20.0); // Energy in one sample to calibrate input noise // the normalized OAI value seems to be 256 as average amplitude (numerical amplification = 1) - const double noise_per_sample = add_noise ? pow(10, channelDesc->noise_power_dB / 10.0) * 256 : 0; - const uint64_t dd = channelDesc->channel_offset; + const double noise_per_sample = pow(10, channelDesc->noise_power_dB / 10.0) * 256; const int nbTx = channelDesc->nb_tx; double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt]; Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); @@ -264,15 +261,8 @@ void rxAddInput(const c16_t *input_sig, // const struct complex *channelModelEnd=channelModel+channelDesc->channel_length; for (int l = 0; l < (int)channelDesc->channel_length; l++) { - // let's assume TS+i >= l - // fixme: the rfsimulator current structure is interleaved antennas - // this has been designed to not have to wait a full block transmission - // but it is not very usefull - // it would be better to split out each antenna in a separate flow - // that will allow to mix ru antennas freely - // (X + cirSize) % cirSize to ensure that index is positive - const int idx = ((TS + i - l - dd) * nbTx + txAnt + CirSize) % CirSize; - const struct complex16 tx16 = input_sig[idx]; + const int idx = i - l + channelDesc->channel_length - 1; + const struct complex16 tx16 = input_sig[txAnt][idx]; rx_tmp.r += tx16.r * channelModel[l].r - tx16.i * channelModel[l].i; rx_tmp.i += tx16.i * channelModel[l].r + tx16.r * channelModel[l].i; } // l @@ -297,12 +287,11 @@ void rxAddInput(const c16_t *input_sig, channelDesc->Doppler_phase_cur[rxAnt] = Doppler_phase_cur; - if ((TS * nbTx) % CirSize + nbSamples <= CirSize) - // Cast to a wrong type for compatibility ! - LOG_D(HW, - "Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", - 10 * log10((double)signal_energy((int32_t *)&input_sig[(TS * nbTx) % CirSize], nbSamples)), - 10 * log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), - channelDesc->path_loss_dB, - 10 * log10(noise_per_sample)); + // Cast to a wrong type for compatibility ! + LOG_D(HW, + "Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", + 10 * log10((double)signal_energy((int32_t *)&input_sig[0], nbSamples)), + 10 * log10((double)signal_energy((int32_t *)after_channel_sig, nbSamples)), + channelDesc->path_loss_dB, + 10 * log10(noise_per_sample)); } diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 7410d10cbb..0ebdef0d6d 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -24,13 +24,11 @@ #ifndef __RFSIMULATOR_H #define __RFSIMULATOR_H #include -void rxAddInput(const c16_t *input_sig, +void rxAddInput(const c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, int nbSamples, - uint64_t TS, - uint32_t CirSize, - bool apply_noise); + uint64_t TS); #endif diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 983f9e4c39..4ad40b96c8 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,7 @@ * When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic */ +#include "PHY/defs_common.h" #include "utils.h" #include #include @@ -50,27 +51,15 @@ extern "C" { #include #include -} #include "rfsimulator.h" +} #include #include #include #include +#include #define PORT 4043 // default TCP port for this simulator -// -// CirSize defines the number of samples inquired for a read cycle -// It is bounded by a slot read capability (which depends on bandwidth and numerology) -// up to multiple slots read to allow I/Q buffering of the I/Q TCP stream -// -// As a rule of thumb: -// -it can't be less than the number of samples for a slot -// -it can range up to multiple slots -// -// The default value is chosen for 10ms buffering which makes 23040*20 = 460800 samples -// The previous value is kept below in comment it was computed for 100ms 1x 20MHz -// #define CirSize 6144000 // 100ms SiSo 20MHz LTE -#define minCirSize 460800 // 10ms SiSo 40Mhz 3/4 sampling NR78 FR1 #define sampleToByte(a, b) ((a) * (b) * sizeof(sample_t)) #define byteToSample(a, b) ((a) / (sizeof(sample_t) * (b))) @@ -141,7 +130,6 @@ static telnetshell_cmddef_t rfsimu_cmdarray[] = { static telnetshell_cmddef_t *setmodel_cmddef = &(rfsimu_cmdarray[1]); static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}}; -static uint64_t CirSize = minCirSize; typedef c16_t sample_t; // 2*16 bits complex number typedef struct beam_switch_command_t { @@ -155,20 +143,25 @@ typedef struct { std::mutex mutex; } beam_state_t; +typedef struct { + samplesBlockHeader_t header; + char payload[1]; +} rfsim_packet_t; + typedef struct buffer_s { int conn_sock; openair0_timestamp lastReceivedTS; bool headerMode; bool trashingPacket; samplesBlockHeader_t th; + uint nbAnt; char *transferPtr; uint64_t remainToTransfer; - char *circularBufEnd; - sample_t *circularBuf; channel_desc_t *channel_model; - char *packet_ptr; + rfsim_packet_t *packet_ptr; size_t payload_sz; size_t remainToTransferBeam; + std::queue received_packets; } buffer_t; typedef struct { @@ -273,6 +266,29 @@ static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timest } } +/** + * @brief Clears old packets from the received_packets queue based on a threshold timestamp. + * + * This function iterates through the received_packets queue and removes packets whose + * timestamp plus size is less than or equal to the provided threshold_timestamp. + * It frees the memory allocated for each removed packet. + * + * @param received_packets Reference to the queue of rfsim_packet_t pointers representing received packets. + * @param threshold_timestamp The timestamp threshold used to determine which packets to remove. + */ +static void clear_old_packets(std::queue &received_packets, uint64_t threshold_timestamp) +{ + while (!received_packets.empty()) { + rfsim_packet_t *pkt = received_packets.front(); + if (pkt->header.timestamp + pkt->header.size <= threshold_timestamp) { + free(pkt); + received_packets.pop(); + } else { + break; + } + } +} + static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time); static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock) @@ -280,18 +296,13 @@ static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock) uint64_t buff_index = bridge->next_buf++ % MAX_FD_RFSIMU; buffer_t *ptr = &bridge->buf[buff_index]; bridge->nb_cnx++; - ptr->circularBuf = static_cast(calloc(1, sampleToByte(CirSize, 1))); - if (ptr->circularBuf == NULL) { - LOG_E(HW, "malloc(%lu) failed\n", sampleToByte(CirSize, 1)); - return NULL; - } - ptr->circularBufEnd = ((char *)ptr->circularBuf) + sampleToByte(CirSize, 1); ptr->conn_sock = sock; ptr->lastReceivedTS = 0; ptr->headerMode = true; ptr->trashingPacket = true; ptr->transferPtr = (char *)&ptr->th; ptr->remainToTransfer = sizeof(samplesBlockHeader_t); + ptr->received_packets = std::queue(); int sendbuff = SEND_BUFF_SIZE; if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff)) != 0) { LOG_E(HW, "setsockopt(SO_SNDBUF) failed\n"); @@ -338,11 +349,11 @@ static void removeCirBuf(rfsimulator_state_t *bridge, buffer_t *buf) LOG_E(HW, "epoll_ctl(EPOLL_CTL_DEL) failed\n"); } close(buf->conn_sock); - free(buf->circularBuf); // Fixme: no free_channel_desc_scm(bridge->buf[sock].channel_model) implemented // a lot of mem leaks // free(bridge->buf[sock].channel_model); - memset(buf, 0, sizeof(buffer_t)); + clear_old_packets(buf->received_packets, INT64_MAX); + *buf = buffer_t{}; buf->conn_sock = -1; bridge->nb_cnx--; } @@ -856,37 +867,17 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, LOG_E(HW, "rfsimulator sending 0 tx antennas\n"); samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beam_map}; fullwrite(b->conn_sock, &header, sizeof(header), t); - if (t->beam_ctrl->enable_beams) { - int num_beams = __builtin_popcountll(beam_map); - AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n"); - sample_t tmpSamples[nsamps][num_beams][nbAnt]; - for (int beam = 0; beam < num_beams; beam++) { - for (int a = 0; a < nbAnt; a++) { - sample_t *in = (sample_t *)samplesVoid[beam][a]; - for (int s = 0; s < nsamps; s++) - tmpSamples[s][beam][a] = in[s]; - } - } - fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt * num_beams), t); - } else { - if (nbAnt == 1) { - fullwrite(b->conn_sock, samplesVoid[0][0], sampleToByte(nsamps, nbAnt), t); - } else { - sample_t tmpSamples[nsamps][nbAnt]; - for (int a = 0; a < nbAnt; a++) { - sample_t *in = (sample_t *)samplesVoid[0][a]; - for (int s = 0; s < nsamps; s++) - tmpSamples[s][a] = in[s]; - } - fullwrite(b->conn_sock, (void *)tmpSamples, sampleToByte(nsamps, nbAnt), t); + int num_beams = __builtin_popcountll(beam_map); + AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n"); + for (int beam = 0; beam < num_beams; beam++) { + for (int a = 0; a < nbAnt; a++) { + sample_t *in = (sample_t *)samplesVoid[beam][a]; + fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t); } } } } - if (t->lastWroteTS != 0 && fabs((double)t->lastWroteTS - timestamp) > (double)CirSize) - LOG_W(HW, "Discontinuous TX gap too large Tx:%lu, %lu\n", t->lastWroteTS, timestamp); - if (t->lastWroteTS > timestamp) LOG_W(HW, "Not supported to send Tx out of order %lu, %lu\n", t->lastWroteTS, timestamp); @@ -995,99 +986,110 @@ static bool add_client(rfsimulator_state_t *t) static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_time) { b->headerMode = false; // We got the header - + AssertFatal(b->th.nbAnt != 0, "Number of antennas not set\n"); + if (b->nbAnt != b->th.nbAnt) { + LOG_A(HW, "RFsim: Number of antennas changed from %d to %d\n", b->nbAnt, b->th.nbAnt); + b->nbAnt = b->th.nbAnt; + } if (first_time) { b->lastReceivedTS = b->th.timestamp; b->trashingPacket = true; } else { if (b->lastReceivedTS < (int64_t)b->th.timestamp) { - // We have a transmission hole to fill, like TDD - // we create no signal samples up to the beginning of this reception int nbAnt = b->th.nbAnt; if (!nbAnt) LOG_E(HW, "rfsimulator receive 0 rx antennas\n"); - if (b->th.timestamp - b->lastReceivedTS < CirSize) { - // case we wrap at circular buffer end - for (uint64_t index = b->lastReceivedTS; index < b->th.timestamp; index++) { - for (int a = 0; a < nbAnt; a++) { - b->circularBuf[(index * nbAnt + a) % CirSize] = (c16_t){0}; - } - } - } else { - // case no circular buffer wrap - memset(b->circularBuf, 0, sampleToByte(CirSize, 1)); - } b->lastReceivedTS = b->th.timestamp; } else if (b->lastReceivedTS > (int64_t)b->th.timestamp) { LOG_W(HW, "Received data in past: current is %lu, new reception: %lu!\n", b->lastReceivedTS, b->th.timestamp); b->trashingPacket = true; } - // verify the time gap is not too large - mutexlock(t->Sockmutex); - if (t->lastWroteTS != 0 && (fabs((double)t->lastWroteTS - b->lastReceivedTS) > (double)CirSize)) - LOG_W(HW, "UEsock(%d) Tx/Rx shift too large Tx:%lu, Rx:%lu\n", b->conn_sock, t->lastWroteTS, b->lastReceivedTS); - mutexunlock(t->Sockmutex); } int num_beams = __builtin_popcountll(b->th.beam_map); AssertFatal(b->th.beam_map == 1ULL || t->beam_ctrl->enable_beams == 1, "The transmitter has enabled beam simulation while this receiver has not\n"); size_t payload_sz = sampleToByte(b->th.size, b->th.nbAnt) * num_beams; - if (b->packet_ptr == NULL || payload_sz > b->payload_sz) { - free(b->packet_ptr); - b->packet_ptr = static_cast(calloc_or_fail(1, payload_sz)); - b->payload_sz = payload_sz; - } - b->transferPtr = b->packet_ptr; + b->packet_ptr = static_cast(calloc_or_fail(1, payload_sz + sizeof(samplesBlockHeader_t))); + b->packet_ptr->header = b->th; + b->transferPtr = b->packet_ptr->payload; b->remainToTransfer = payload_sz; return; } -static void process_recv(rfsimulator_state_t *t, buffer_t *b, bool first_time) +static std::vector get_beam_ids(uint64_t beam_map) { - int num_beams = __builtin_popcountll(b->th.beam_map); + int num_beams = __builtin_popcountll(beam_map); AssertFatal(num_beams > 0, "Needs at least one beam\n"); - int num_ant = b->th.nbAnt; - int num_samples = b->th.size; - int tx_beam_ids[MAX_BEAMS]; - int *tx_beam_ids_p = tx_beam_ids; + std::vector beam_ids; for (int i = 0; i < MAX_BEAMS; i++) { - if (b->th.beam_map & (1ULL << i)) { - *tx_beam_ids_p++ = i; + if (beam_map & (1ULL << i)) { + beam_ids.push_back(i); } } - c16_t *in = (c16_t *)b->packet_ptr; - int num_samples_to_process = num_samples; - openair0_timestamp first_sample_timestamp = b->th.timestamp; - while (num_samples_to_process > 0) { - uint32_t nsamps_out; - uint64_t rx_beam_map = get_beam_map(&t->beam_ctrl->rx, first_sample_timestamp, num_samples_to_process, &nsamps_out); - AssertFatal(__builtin_popcountll(rx_beam_map) == 1, "Only supports 1 beam\n"); - int rx_beam_id = 0; - for (int i = 0; i < MAX_BEAMS; i++) { - if (rx_beam_map & (1ULL << i)) { - rx_beam_id = i; - break; - } + return beam_ids; +} + +/** + * @brief Combines samples from received packets into a single buffer for the rx beam + * + * This function processes the received_packets queue and combines the transmitted beams + * into a single buffer for each antenna. It applies the appropriate gain and handles + * overlapping timestamps. + * + * + * @param t Pointer to the rfsimulator_state_t structure. + * @param received_packets Reference to the queue of rfsim_packet_t pointers representing received packets. + * @param start_timestamp The start timestamp for the combination process. + * @param num_aatx The number of antennas to process. + * @param num_samples The number of samples to process. + * @param rx_beam_id Receiver configured beam id for the timestamp + * @return A vector of vectors containing the combined samples for each antenna. + */ +static std::vector> combine_received_beams(rfsimulator_state_t *t, + std::queue &received_packets, + uint64_t start_timestamp, + int num_aatx, + size_t num_samples, + int rx_beam_id) +{ + // Assume received_packets is ordered by timestamp + std::vector> ant_buffers(num_aatx, std::vector(num_samples, {0, 0})); + std::queue packets_copy = received_packets; + while (!packets_copy.empty()) { + rfsim_packet_t *pkt = packets_copy.front(); + if (pkt->header.timestamp + pkt->header.size <= start_timestamp) { + // This packet is before the start timestamp, discard it + packets_copy.pop(); + continue; } - for (uint i = 0; i < nsamps_out; i++) { - for (int aatx = 0; aatx < num_ant; aatx++) { - c16_t *out = &b->circularBuf[((first_sample_timestamp + i) * num_ant + aatx) % CirSize]; - cf_t sample = {.r = 0, .i = 0}; - for (int beam = 0; beam < num_beams; beam++) { - float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beam_ids[beam]); - float gain_linear = powf(10, gain_dB / 20.0); - sample.r += in->r * gain_linear; - sample.i += in->i * gain_linear; - in++; + if (pkt->header.timestamp > start_timestamp + num_samples) { + // This packet is after the end of the buffer, stop processing + break; + } + + std::vector tx_beams = get_beam_ids(pkt->header.beam_map); + for (uint beam = 0; beam < tx_beams.size(); beam++) { + float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beams[beam]); + float gain_linear = powf(10, gain_dB / 20.0); + uint64_t overlap_start = std::max(start_timestamp, pkt->header.timestamp); + uint64_t overlap_end = std::min(start_timestamp + num_samples, pkt->header.timestamp + pkt->header.size); + int write_start_idx = overlap_start - start_timestamp; + int write_end_idx = overlap_end - start_timestamp; + int read_start_idx = overlap_start - pkt->header.timestamp; + for (int aatx = 0; aatx < num_aatx; aatx++) { + c16_t *buffer = (c16_t *)pkt->payload; + c16_t *tx_ant_buffer_in = &buffer[(num_aatx * beam + aatx) * pkt->header.size + read_start_idx]; + for (int s = write_start_idx; s < write_end_idx; s++) { + ant_buffers[aatx][s].r += tx_ant_buffer_in->r * gain_linear; + ant_buffers[aatx][s].i += tx_ant_buffer_in->i * gain_linear; + tx_ant_buffer_in++; } - out->r = sample.r; - out->i = sample.i; } } - first_sample_timestamp += nsamps_out; - num_samples_to_process -= nsamps_out; + packets_copy.pop(); } + return ant_buffers; } static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) @@ -1118,7 +1120,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) } } - if (b->circularBuf == NULL) { + if (b->conn_sock == -1) { LOG_E(HW, "Received data on not connected socket %d\n", events[nbEv].data.fd); continue; } @@ -1142,10 +1144,13 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) b->remainToTransfer = sizeof(samplesBlockHeader_t); if (!b->trashingPacket) { - process_recv(t, b, first_time); b->lastReceivedTS = b->th.timestamp + b->th.size; LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); + b->received_packets.emplace(b->packet_ptr); + } else { + free(b->packet_ptr); } + b->packet_ptr = NULL; b->trashingPacket = false; } } @@ -1167,7 +1172,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest int first_sock; for (first_sock = 0; first_sock < MAX_FD_RFSIMU; first_sock++) - if (t->buf[first_sock].circularBuf != NULL) + if (t->buf[first_sock].conn_sock != -1) break; if (first_sock == MAX_FD_RFSIMU) { @@ -1194,7 +1199,7 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest buffer_t *b = NULL; for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { b = &t->buf[sock]; - if (b->circularBuf && (t->nextRxTstamp + nsamps) > b->lastReceivedTS) { + if (b->conn_sock != -1 && (t->nextRxTstamp + nsamps) > b->lastReceivedTS) { have_to_wait = true; break; } @@ -1221,13 +1226,15 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest for (int a = 0; a < nbAnt; a++) memset(samplesVoid[a], 0, sampleToByte(nsamps, 1)); cf_t temp_array[nbAnt][nsamps]; - bool apply_noise_per_channel = get_noise_power_dBFS() == INVALID_DBFS_VALUE; - int num_chanmod_channels = 0; + memset(temp_array, 0, sizeof(temp_array)); // Add all input nodes signal in the output buffer for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { buffer_t *ptr = &t->buf[sock]; - if (ptr->circularBuf && !ptr->trashingPacket) { + uint64_t timestamp_processed = t->nextRxTstamp; + + if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { + AssertFatal(ptr->nbAnt != 0, "Number of antennas not set\n"); bool reGenerateChannel = false; // fixme: when do we regenerate @@ -1235,70 +1242,69 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest if (reGenerateChannel) random_channel(ptr->channel_model, 0); - for (int a_rx = 0; a_rx < nbAnt; a_rx++) { // loop over number of Rx antennas - if (ptr->channel_model != NULL) { // apply a channel model - if (num_chanmod_channels == 0) { - memset(temp_array, 0, sizeof(temp_array)); + std::vector rx_beams_ids = get_beam_ids(t->beam_ctrl->rx.beam_map); + + if (ptr->channel_model != NULL) { // apply a channel model + const uint64_t dd = ptr->channel_model->channel_offset; + const uint64_t channel_length = ptr->channel_model->channel_length; + timestamp_processed = t->nextRxTstamp - dd; + for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { + std::vector> ant_buffers = combine_received_beams(t, + ptr->received_packets, + t->nextRxTstamp - dd, + ptr->nbAnt, + nsamps + channel_length - 1, + rx_beams_ids[beam]); + const c16_t *input[ant_buffers.size()]; + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + input[aatx] = ant_buffers[aatx].data(); } - num_chanmod_channels++; - rxAddInput(ptr->circularBuf, - temp_array[a_rx], - a_rx, - ptr->channel_model, - nsamps, - t->nextRxTstamp, - CirSize, - apply_noise_per_channel); - } else { // no channel modeling - int nbAnt_tx = ptr->th.nbAnt; // number of Tx antennas - int firstIndex = (CirSize + t->nextRxTstamp - t->chan_offset) % CirSize; - sample_t *out = (sample_t *)samplesVoid[a_rx]; - if (nbAnt_tx == 1 && t->nb_cnx == 1) { // optimized for 1 Tx and 1 UE - sample_t *firstSample = (sample_t *)&(ptr->circularBuf[firstIndex]); - if ((uint64_t)firstIndex + (uint64_t)nsamps > CirSize) { - int tailSz = CirSize - firstIndex; - memcpy(out, firstSample, sampleToByte(tailSz, 1)); - memcpy(out + tailSz, &ptr->circularBuf[0], sampleToByte(nsamps - tailSz, 1)); - } else { - memcpy(out, firstSample, sampleToByte(nsamps, 1)); + for (int aarx = 0; aarx < nbAnt; aarx++) { + rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, t->nextRxTstamp); + } + } + } else { + for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { + std::vector> ant_buffers = + combine_received_beams(t, ptr->received_packets, t->nextRxTstamp, ptr->th.nbAnt, nsamps, rx_beams_ids[beam]); + for (int aarx = 0; aarx < nbAnt; aarx++) { + double H_awgn_mimo_coeff[ant_buffers.size()]; + for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { + uint32_t ant_diff = std::abs(aatx - aarx); + H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; } - } else { - // SIMD (with simde) optimization might be added here later - LOG_D(HW, "nbAnt_tx %d nbAnt %d\n", nbAnt_tx, nbAnt); - double H_awgn_mimo_coeff[nbAnt_tx]; - for (int a_tx = 0; a_tx < nbAnt_tx; a_tx++) { - uint32_t ant_diff = abs(a_tx - a_rx); - H_awgn_mimo_coeff[a_tx] = ant_diff ? (0.2 / ant_diff) : 1.0; - } - for (int i = 0; i < nsamps; i++) { // loop over nsamps - for (int a_tx = 0; a_tx < nbAnt_tx; a_tx++) { // sum up signals from nbAnt_tx antennas - out[i].r += (short)(ptr->circularBuf[((firstIndex + i) * nbAnt_tx + a_tx) % CirSize].r * H_awgn_mimo_coeff[a_tx]); - out[i].i += (short)(ptr->circularBuf[((firstIndex + i) * nbAnt_tx + a_tx) % CirSize].i * H_awgn_mimo_coeff[a_tx]); + + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas + c16_t *out = (sample_t *)samplesVoid[aarx]; + for (int i = 0; i < nsamps; i++) { // loop over nsamps + out[i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; + out[i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; } // end for a_tx } // end for i (number of samps) - } // end of 1 tx antenna optimization - } // end of no channel modeling - } // end for a (number of rx antennas) + } // end for a_rx + } + } + } + + clear_old_packets(ptr->received_packets, timestamp_processed); + } + + bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; + if (apply_global_noise) { + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); + temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); + temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); + } } } - if (apply_noise_per_channel && num_chanmod_channels > 0) { - // Noise is already applied through the channel model - for (int a = 0; a < nbAnt; a++) { - sample_t *out = (sample_t *)samplesVoid[a]; - for (int i = 0; i < nsamps; i++) { - out[i].r += lroundf(temp_array[a][i].r); - out[i].i += lroundf(temp_array[a][i].i); - } - } - } else if (num_chanmod_channels > 0) { - // Apply noise from global setting - int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); - for (int a = 0; a < nbAnt; a++) { - sample_t *out = (sample_t *)samplesVoid[a]; - for (int i = 0; i < nsamps; i++) { - out[i].r += lroundf(temp_array[a][i].r + noise_power * gaussZiggurat(0.0, 1.0)); - out[i].i += lroundf(temp_array[a][i].i + noise_power * gaussZiggurat(0.0, 1.0)); - } + + for (int a = 0; a < nbAnt; a++) { + sample_t *out = (sample_t *)samplesVoid[a]; + for (int i = 0; i < nsamps; i++) { + out[i].r += lroundf(temp_array[a][i].r); + out[i].i += lroundf(temp_array[a][i].i); } } @@ -1345,6 +1351,8 @@ static void rfsimulator_end(openair0_device *device) if (b->conn_sock >= 0) removeCirBuf(s, b); } + clear_beam_queue(&s->beam_ctrl->tx, INT64_MAX); + clear_beam_queue(&s->beam_ctrl->rx, INT64_MAX); close(s->epollfd); free(s); } @@ -1391,10 +1399,6 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d if (rfsimulator->prop_delay_ms > 0.0) rfsimulator->chan_offset = ceil(rfsimulator->sample_rate * rfsimulator->prop_delay_ms / 1000); if (rfsimulator->chan_offset != 0) { - if (CirSize < minCirSize + rfsimulator->chan_offset) { - CirSize = minCirSize + rfsimulator->chan_offset; - LOG_I(HW, "CirSize = %lu\n", CirSize); - } rfsimulator->prop_delay_ms = rfsimulator->chan_offset * 1000 / rfsimulator->sample_rate; LOG_I(HW, "propagation delay %f ms, %lu samples\n", rfsimulator->prop_delay_ms, rfsimulator->chan_offset); } From 643be87832d90e1a22c69a1b23df555152db4ad2 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Oct 2025 13:27:46 +0200 Subject: [PATCH 31/43] RFsim: read_beams function Added a new function for reading multiple beams concurrently. The TX to RX beam combination is simplistic and based on beam gain. --- radio/COMMON/common_lib.h | 22 +++- radio/rfsimulator/simulator.cpp | 213 +++++++++++++++++++------------- 2 files changed, 150 insertions(+), 85 deletions(-) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index 3fd9d6243d..cb1b0c8ddc 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -535,7 +535,27 @@ struct openair0_device_t { * antennas from which to receive samples \returns the number of sample read */ - int (*trx_read_func)(openair0_device *device, openair0_timestamp *ptimestamp, void **buff, int nsamps,int num_antennas); + int (*trx_read_func)(openair0_device *device, openair0_timestamp *ptimestamp, void **buff, int nsamps, int num_antennas); + + /*! \brief Receive samples from hardware. + * Read nsamps samples from each channel to buffers. buff[0] is the array for + * the first channel. *ptimestamp is the time at which the first sample + * was received. + * \param device the hardware to use + * \param[out] ptimestamp the time at which the first sample was received. + * \param[out] buff An array of pointers to buffers for received samples. The buffers must be large enough to hold the number of + * samples nsamps. + * \param nsamps Number of samples. One sample is 2 byte I + 2 byte Q => 4 byte. + * \param num_antennas number of antennas from which to receive samples + * \param num_beams number of beams from which to receive samples + * \returns the number of sample read + */ + int (*trx_read_beams_func)(openair0_device *device, + openair0_timestamp *ptimestamp, + void ***buff, + int nsamps, + int num_antennas, + int num_beams); /*! \brief Receive samples from hardware, this version provides a single antenna at a time and returns. * Read nsamps samples from each channel to buffers. buff[0] is the array for diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 4ad40b96c8..5c01322fa6 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -27,6 +27,7 @@ * When the opposite side switch from passive reading to active R+Write, the synchro is not fully deterministic */ +#include "PHY/TOOLS/tools_defs.h" #include "PHY/defs_common.h" #include "utils.h" #include @@ -1158,7 +1159,90 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) return nfds > 0; } -static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) +static void rfsimulator_read_internal(rfsimulator_state_t *t, + c16_t **samples, + openair0_timestamp timestamp, + int nsamps, + int nbAnt, + int rx_beam_id) +{ + cf_t temp_array[nbAnt][nsamps]; + memset(temp_array, 0, sizeof(temp_array)); + // Add all input nodes signal in the output buffer + for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { + buffer_t *ptr = &t->buf[sock]; + + if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { + AssertFatal(ptr->nbAnt != 0, "Number of antennas not set\n"); + bool reGenerateChannel = false; + + // fixme: when do we regenerate + // it seems legacy behavior is: never in UL, each frame in DL + if (reGenerateChannel) + random_channel(ptr->channel_model, 0); + + if (ptr->channel_model != NULL) { // apply a channel model + const uint64_t dd = ptr->channel_model->channel_offset; + const uint64_t channel_length = ptr->channel_model->channel_length; + std::vector> ant_buffers = combine_received_beams(t, + ptr->received_packets, + timestamp - dd - (channel_length - 1), + ptr->nbAnt, + nsamps + channel_length - 1, + rx_beam_id); + const c16_t *input[ant_buffers.size()]; + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + input[aatx] = ant_buffers[aatx].data(); + } + for (int aarx = 0; aarx < nbAnt; aarx++) { + rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, timestamp); + } + } else { + std::vector> ant_buffers = + combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id); + for (int aarx = 0; aarx < nbAnt; aarx++) { + double H_awgn_mimo_coeff[ant_buffers.size()]; + for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { + uint32_t ant_diff = std::abs(aatx - aarx); + H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; + } + + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas + for (int i = 0; i < nsamps; i++) { // loop over nsamps + samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; + samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; + } // end for a_tx + } // end for i (number of samps) + } // end for a_rx + } + } + } + + bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; + if (apply_global_noise) { + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); + temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); + temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); + } + } + } + + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + samples[a][i].r += lroundf(temp_array[a][i].r); + samples[a][i].i += lroundf(temp_array[a][i].i); + } + } +} + +static int rfsimulator_read_beams(openair0_device *device, + openair0_timestamp *ptimestamp, + void ***samplesVoid, + int nsamps, + int nbAnt, + int num_beams) { rfsimulator_state_t *t = static_cast(device->priv); LOG_D(HW, @@ -1181,8 +1265,9 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest LOG_I(HW, "No connected device, generating void samples...\n"); if (!flushInput(t, t->wait_timeout, false)) { - for (int x = 0; x < nbAnt; x++) - memset(samplesVoid[x], 0, sampleToByte(nsamps, 1)); + for (int beam = 0; beam < num_beams; beam++) + for (int x = 0; x < nbAnt; x++) + memset(samplesVoid[beam][x], 0, sampleToByte(nsamps, 1)); t->nextRxTstamp += nsamps; @@ -1223,89 +1308,31 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest t->poll_telnetcmdq(t->telnetcmd_qid, t); // Clear the output buffer - for (int a = 0; a < nbAnt; a++) - memset(samplesVoid[a], 0, sampleToByte(nsamps, 1)); - cf_t temp_array[nbAnt][nsamps]; - memset(temp_array, 0, sizeof(temp_array)); - // Add all input nodes signal in the output buffer - for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { - buffer_t *ptr = &t->buf[sock]; + for (int beam = 0; beam < num_beams; beam++) + for (int a = 0; a < nbAnt; a++) + memset(samplesVoid[beam][a], 0, sampleToByte(nsamps, 1)); - uint64_t timestamp_processed = t->nextRxTstamp; - - if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { - AssertFatal(ptr->nbAnt != 0, "Number of antennas not set\n"); - bool reGenerateChannel = false; - - // fixme: when do we regenerate - // it seems legacy behavior is: never in UL, each frame in DL - if (reGenerateChannel) - random_channel(ptr->channel_model, 0); - - std::vector rx_beams_ids = get_beam_ids(t->beam_ctrl->rx.beam_map); - - if (ptr->channel_model != NULL) { // apply a channel model - const uint64_t dd = ptr->channel_model->channel_offset; - const uint64_t channel_length = ptr->channel_model->channel_length; - timestamp_processed = t->nextRxTstamp - dd; - for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { - std::vector> ant_buffers = combine_received_beams(t, - ptr->received_packets, - t->nextRxTstamp - dd, - ptr->nbAnt, - nsamps + channel_length - 1, - rx_beams_ids[beam]); - const c16_t *input[ant_buffers.size()]; - for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { - input[aatx] = ant_buffers[aatx].data(); - } - for (int aarx = 0; aarx < nbAnt; aarx++) { - rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, t->nextRxTstamp); - } - } - } else { - for (uint beam = 0; beam < rx_beams_ids.size(); beam++) { - std::vector> ant_buffers = - combine_received_beams(t, ptr->received_packets, t->nextRxTstamp, ptr->th.nbAnt, nsamps, rx_beams_ids[beam]); - for (int aarx = 0; aarx < nbAnt; aarx++) { - double H_awgn_mimo_coeff[ant_buffers.size()]; - for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { - uint32_t ant_diff = std::abs(aatx - aarx); - H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; - } - - for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas - c16_t *out = (sample_t *)samplesVoid[aarx]; - for (int i = 0; i < nsamps; i++) { // loop over nsamps - out[i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; - out[i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; - } // end for a_tx - } // end for i (number of samps) - } // end for a_rx - } + openair0_timestamp timestamp = t->nextRxTstamp; + int nsamps_to_process = nsamps; + while (nsamps_to_process > 0) { + uint32_t nsamps_beam_map; + uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map); + std::vector rx_beam_ids = get_beam_ids(beam_map); + if ((int)rx_beam_ids.size() != num_beams) { + LOG_D(HW, + "Number of beams does not match application request num_beams %d, beam_map beams %lu\n", + num_beams, + rx_beam_ids.size()); + } + for (int beam = 0; beam < num_beams && beam < (int)rx_beam_ids.size(); beam++) { + c16_t *samples_beam[nbAnt]; + for (int i = 0; i < nbAnt; i++) { + samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp; } + rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam]); } - - clear_old_packets(ptr->received_packets, timestamp_processed); - } - - bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; - if (apply_global_noise) { - for (int a = 0; a < nbAnt; a++) { - for (int i = 0; i < nsamps; i++) { - int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); - temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); - temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); - } - } - } - - for (int a = 0; a < nbAnt; a++) { - sample_t *out = (sample_t *)samplesVoid[a]; - for (int i = 0; i < nsamps; i++) { - out[i].r += lroundf(temp_array[a][i].r); - out[i].i += lroundf(temp_array[a][i].i); - } + timestamp += nsamps_beam_map; + nsamps_to_process -= nsamps_beam_map; } struct timespec end_time; @@ -1327,14 +1354,31 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest nsamps, *ptimestamp, t->nextRxTstamp, - signal_energy(static_cast(samplesVoid[0]), nsamps)); + signal_energy(static_cast(samplesVoid[0][0]), nsamps)); /* trace only first antenna */ T(T_USRP_RX_ANT0, T_INT(t->nextRxTstamp), T_BUFFER(samplesVoid[0], (int)sampleToByte(nsamps, 1))); + for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { + buffer_t *ptr = &t->buf[sock]; + + if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { + openair0_timestamp timestamp_to_free = t->nextRxTstamp - 1; + if (ptr->channel_model) { + timestamp_to_free -= ptr->channel_model->channel_length - ptr->channel_model->channel_offset; + } + clear_old_packets(ptr->received_packets, timestamp_to_free); + } + } + return nsamps; } +static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) +{ + return rfsimulator_read_beams(device, ptimestamp, &samplesVoid, nsamps, nbAnt, 1); +} + static int rfsimulator_get_stats(openair0_device *device) { return 0; @@ -1417,6 +1461,7 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d device->trx_write_func = rfsimulator_write; device->trx_write_beams_func = rfsimulator_write_beams; device->trx_read_func = rfsimulator_read; + device->trx_read_beams_func = rfsimulator_read_beams; /* let's pretend to be a b2x0 */ device->type = RFSIMULATOR; openair0_cfg[0].rx_gain[0] = 0; From 435dfd57d82186b4ba79598449fbfffeb7e449f7 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 14 Oct 2025 13:43:43 +0200 Subject: [PATCH 32/43] RFsim: Optimization for operation without chanmod --- radio/rfsimulator/simulator.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 5c01322fa6..a940f6f023 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1167,7 +1167,7 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, int rx_beam_id) { cf_t temp_array[nbAnt][nsamps]; - memset(temp_array, 0, sizeof(temp_array)); + bool channel_modelling = false; // Add all input nodes signal in the output buffer for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { buffer_t *ptr = &t->buf[sock]; @@ -1182,6 +1182,10 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, random_channel(ptr->channel_model, 0); if (ptr->channel_model != NULL) { // apply a channel model + if (!channel_modelling) { + memset(temp_array, 0, sizeof(temp_array)); + channel_modelling = true; + } const uint64_t dd = ptr->channel_model->channel_offset; const uint64_t channel_length = ptr->channel_model->channel_length; std::vector> ant_buffers = combine_received_beams(t, @@ -1220,19 +1224,25 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; if (apply_global_noise) { + if (!channel_modelling) { + memset(temp_array, 0, sizeof(temp_array)); + channel_modelling = true; + } + int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); for (int a = 0; a < nbAnt; a++) { for (int i = 0; i < nsamps; i++) { - int16_t noise_power = (int16_t)(32767.0 / powf(10.0, .05 * -get_noise_power_dBFS())); temp_array[a][i].r += noise_power + gaussZiggurat(0.0, 1.0); temp_array[a][i].i += noise_power * gaussZiggurat(0.0, 1.0); } } } - for (int a = 0; a < nbAnt; a++) { - for (int i = 0; i < nsamps; i++) { - samples[a][i].r += lroundf(temp_array[a][i].r); - samples[a][i].i += lroundf(temp_array[a][i].i); + if (channel_modelling) { + for (int a = 0; a < nbAnt; a++) { + for (int i = 0; i < nsamps; i++) { + samples[a][i].r += lroundf(temp_array[a][i].r); + samples[a][i].i += lroundf(temp_array[a][i].i); + } } } } From 05de66d811c448d419aadc7580187a7392d20e1a Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 17 Oct 2025 08:24:27 +0200 Subject: [PATCH 33/43] Make RFSIMULATOR_PARAMS_DESC C++ compatible. Add a set of macros with designated initializers to simplify using paramdef_t arrays in C++ source code. Fix clang error with RFSIMULATOR_PARAMS_DESC using mixed designated and non-designated initializers. --- common/config/config_paramdesc.h | 44 ++++++++++++++++++++++++++++++++ radio/rfsimulator/simulator.cpp | 42 +++++++++++++++--------------- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/common/config/config_paramdesc.h b/common/config/config_paramdesc.h index 904b9dfcb0..9333a778af 100644 --- a/common/config/config_paramdesc.h +++ b/common/config/config_paramdesc.h @@ -161,6 +161,50 @@ typedef struct paramdef { #define TYPE_LIST 55 #define ANY_IPV4ADDR_STRING "0.0.0.0" + +// Macros to simplify defining parameters in paramdef_t arrays in C++ source code +#define OPTNAME(x) .optname = x +#define HELPSTR(x) .helpstr = x +#define PARAMFLAG(x) .paramflags = x +#define PARAMTYPE(x) .type = x +#define STRINGPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .strptr = ptr, .defstrval = defval, PARAMTYPE(TYPE_STRING), .numelt = 0 \ + } +#define DOUBLEPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .dblptr = ptr, .defdblval = defval, PARAMTYPE(TYPE_DOUBLE), .numelt = 0 \ + } +#define INTPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .iptr = ptr, .defintval = defval, PARAMTYPE(TYPE_INT), .numelt = 0 \ + } +#define UINT16PARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .u16ptr = ptr, .defuintval = defval, PARAMTYPE(TYPE_UINT16), .numelt = 0 \ + } +#define STRLISTPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .strlistptr = ptr, .defstrlistval = defval, PARAMTYPE(TYPE_STRINGLIST), \ + .numelt = 0 \ + } +#define INT64PARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .i64ptr = ptr, .defint64val = defval, PARAMTYPE(TYPE_INT64), .numelt = 0 \ + } +#define INTPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .iptr = ptr, .defintval = defval, PARAMTYPE(TYPE_INT), .numelt = 0 \ + } +#define BOOLPARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .iptr = ptr, .defintval = defval, PARAMTYPE(TYPE_INT), .numelt = 0 \ + } +#define UINT64PARAM(name, help, flags, ptr, defval) \ + { \ + OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .u64ptr = ptr, .defuintval = defval, PARAMTYPE(TYPE_UINT64), .numelt = 0 \ + } + typedef struct paramlist_def { char listname[MAX_OPTNAME_SIZE]; paramdef_t **paramarray; diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index a940f6f023..e06e9f5bab 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -83,29 +83,29 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; " chanmod: enable channel modelisation\n" \ " saviq: enable saving written iqs to a file\n" -// clang-format off -/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -/* configuration parameters for the rfsimulator device */ -/* optname helpstr paramflags XXXptr defXXXval type numelt */ -/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ #define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED +// clang-format off +/*----------------------------------------------------------------------------------------------------------------------------------------------------*/ +/* configuration parameters for the rfsimulator device */ +/* optname helpstr paramflags XXXptr defXXXval */ +/*----------------------------------------------------------------------------------------------------------------------------------------------------*/ #define RFSIMULATOR_PARAMS_DESC { \ - {"serveraddr", "\n", simOpt, .strptr=&rfsimulator->ip, .defstrval="127.0.0.1", TYPE_STRING, 0 },\ - {"serverport", "\n", simOpt, .u16ptr = &(rfsimulator->port), .defuintval = PORT, TYPE_UINT16, 0}, \ - {RFSIMU_OPTIONS_PARAMNAME, RFSIM_CONFIG_HELP_OPTIONS, 0, .strlistptr = NULL, .defstrlistval = NULL, TYPE_STRINGLIST, 0}, \ - {"IQfile", "\n",simOpt, .strptr=&saveF, .defstrval="/tmp/rfsimulator.iqs",TYPE_STRING, 0 },\ - {"modelname", "\n", simOpt, .strptr = &modelname, .defstrval = "AWGN", TYPE_STRING, 0}, \ - {"ploss", "\n", simOpt, .dblptr = &(rfsimulator->chan_pathloss), .defdblval = 0, TYPE_DOUBLE, 0}, \ - {"forgetfact", "\n", simOpt, .dblptr=&(rfsimulator->chan_forgetfact),.defdblval=0, TYPE_DOUBLE, 0 },\ - {"offset", "\n", simOpt, .u64ptr = &(rfsimulator->chan_offset), .defint64val = 0, TYPE_UINT64, 0}, \ - {"prop_delay", "\n", simOpt, .dblptr=&(rfsimulator->prop_delay_ms), .defdblval=0.0, TYPE_DOUBLE, 0 },\ - {"wait_timeout", "\n", simOpt, .iptr=&(rfsimulator->wait_timeout), .defintval=1, TYPE_INT, 0 },\ - {"enable_beams", "enable simplified beam simulation", PARAMFLAG_BOOL, .iptr=&(beam_ctrl->enable_beams), .defintval=0, TYPE_INT, 0},\ - {"num_concurrent_beams", "number of concurrent beams supported", simOpt, .iptr=&(beam_ctrl->num_concurrent_beams), .defintval=1, TYPE_INT, 0},\ - {"tx_beam_map", "initial beam map", simOpt, .u64ptr=&(beam_ctrl->tx.beam_map), .defintval=1, TYPE_UINT64, 0},\ - {"rx_beam_map", "initial beam map", simOpt, .u64ptr=&(beam_ctrl->rx.beam_map), .defintval=1, TYPE_UINT64, 0},\ - {RFSIMU_BEAM_GAINS, "beam gain matrix in toeplitz form (first row only)", simOpt, .strptr=NULL, .defstrval=NULL, TYPE_STRING, 0}, \ - }; + STRINGPARAM("serveraddr", "\n", simOpt, &rfsimulator->ip, "127.0.0.1"), \ + UINT16PARAM("serverport", "\n", simOpt, &rfsimulator->port, PORT), \ + STRLISTPARAM(RFSIMU_OPTIONS_PARAMNAME, RFSIM_CONFIG_HELP_OPTIONS, simOpt, NULL, NULL), \ + STRINGPARAM("IQfile", "\n", simOpt, &saveF, "/tmp/rfsimulator.iqs"),\ + STRINGPARAM("modelname", "\n", simOpt, &modelname, "AWGN"), \ + DOUBLEPARAM("ploss", "\n", simOpt, &rfsimulator->chan_pathloss, 0), \ + DOUBLEPARAM("forgetfact", "\n", simOpt, &rfsimulator->chan_forgetfact, 0), \ + UINT64PARAM("offset", "\n", simOpt, &rfsimulator->chan_offset, 0L), \ + DOUBLEPARAM("prop_delay", "\n", simOpt, &(rfsimulator->prop_delay_ms), 0.0), \ + INTPARAM("wait_timeout", "\n", simOpt, &rfsimulator->wait_timeout, 1), \ + BOOLPARAM("enable_beams", "\n", simOpt, &(beam_ctrl->enable_beams), 0), \ + INTPARAM("num_concurrent_beams", "\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \ + UINT64PARAM("tx_beam_map", "\n", simOpt, &beam_ctrl->tx.beam_map, 1), \ + UINT64PARAM("rx_beam_map", "\n", simOpt, &beam_ctrl->rx.beam_map, 1), \ + STRINGPARAM(RFSIMU_BEAM_GAINS, "\n", simOpt, NULL, NULL), \ +}; // clang-format on static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); extern int get_currentchannels_type(const char *buf, From c587aab6b6933dab2750122ef3c8c4f2fbef69b2 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 17 Oct 2025 14:39:50 +0200 Subject: [PATCH 34/43] RFsim: Fix freeing packets in case of non-negative channel offset In rfsim, received packets are dynamically allocated and freed when no longer needed, which is dependent on the channel model, packet timestamp and packet size (number of samples carried). This commit fixes the packet age check for channels with nonzero channel offset (most notalby NTN channels). Also present: - extract the NTN satellite position update code to a separate function and call it once per trx_read call instead of once per rx beam. --- radio/rfsimulator/apply_channelmod.c | 44 +++++++++++++++------------- radio/rfsimulator/rfsimulator.h | 2 +- radio/rfsimulator/simulator.cpp | 14 +++++++-- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 2eaa6a66c2..2dac26c4e6 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -27,29 +27,9 @@ #include "openair2/LAYER2/NR_MAC_gNB/mac_config.h" #include "rfsimulator.h" -/* - Legacy study: - The parameters are: - gain&loss (decay, signal power, ...) - either a fixed gain in dB, a target power in dBm or ACG (automatic control gain) to a target average - => don't redo the AGC, as it was used in UE case, that must have a AGC inside the UE - will be better to handle the "set_gain()" called by UE to apply it's gain (enable test of UE power loop) - lin_amp = pow(10.0,.05*txpwr_dBm)/sqrt(nb_tx_antennas); - a lot of operations in legacy, grouped in one simulation signal decay: txgain*decay*rxgain - - multi_path (auto convolution, ISI, ...) - either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes - legacy: we regenerate each sub frame in UL, and each frame only in DL -*/ -void rxAddInput(const c16_t **input_sig, - cf_t *after_channel_sig, - int rxAnt, - channel_desc_t *channelDesc, - int nbSamples, - uint64_t TS) +void update_channel_model(channel_desc_t *channelDesc, uint64_t TS) { static uint64_t last_TS = 0; - if ((channelDesc->sat_height > 0) && (channelDesc->enable_dynamic_delay || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit @@ -239,7 +219,29 @@ void rxAddInput(const c16_t **input_sig, } } } +} +/* + Legacy study: + The parameters are: + gain&loss (decay, signal power, ...) + either a fixed gain in dB, a target power in dBm or ACG (automatic control gain) to a target average + => don't redo the AGC, as it was used in UE case, that must have a AGC inside the UE + will be better to handle the "set_gain()" called by UE to apply it's gain (enable test of UE power loop) + lin_amp = pow(10.0,.05*txpwr_dBm)/sqrt(nb_tx_antennas); + a lot of operations in legacy, grouped in one simulation signal decay: txgain*decay*rxgain + + multi_path (auto convolution, ISI, ...) + either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes + legacy: we regenerate each sub frame in UL, and each frame only in DL +*/ +void rxAddInput(const c16_t **input_sig, + cf_t *after_channel_sig, + int rxAnt, + channel_desc_t *channelDesc, + int nbSamples, + uint64_t TS) +{ // channelDesc->path_loss_dB should contain the total path gain // so, in actual RF: tx gain + path loss + rx gain (+antenna gain, ...) // UE and NB gain control to be added diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 0ebdef0d6d..90c2be2f82 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -30,5 +30,5 @@ void rxAddInput(const c16_t **input_sig, channel_desc_t *channelDesc, int nbSamples, uint64_t TS); - +void update_channel_model(channel_desc_t *channelDesc, uint64_t TS); #endif diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index e06e9f5bab..793dfedf18 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1186,11 +1186,11 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, memset(temp_array, 0, sizeof(temp_array)); channel_modelling = true; } - const uint64_t dd = ptr->channel_model->channel_offset; + const uint64_t channel_offset = ptr->channel_model->channel_offset; const uint64_t channel_length = ptr->channel_model->channel_length; std::vector> ant_buffers = combine_received_beams(t, ptr->received_packets, - timestamp - dd - (channel_length - 1), + timestamp - channel_offset - (channel_length - 1), ptr->nbAnt, nsamps + channel_length - 1, rx_beam_id); @@ -1314,6 +1314,14 @@ static int rfsimulator_read_beams(openair0_device *device, int ret = clock_gettime(CLOCK_REALTIME, &start_time); AssertFatal(ret == 0, "clock_gettime() failed: errno %d, %s\n", errno, strerror(errno)); + for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { + buffer_t *ptr = &t->buf[sock]; + + if (ptr->conn_sock != -1 && ptr->channel_model != NULL) { + update_channel_model(ptr->channel_model, t->nextRxTstamp); + } + } + if (t->poll_telnetcmdq) t->poll_telnetcmdq(t->telnetcmd_qid, t); @@ -1375,7 +1383,7 @@ static int rfsimulator_read_beams(openair0_device *device, if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { openair0_timestamp timestamp_to_free = t->nextRxTstamp - 1; if (ptr->channel_model) { - timestamp_to_free -= ptr->channel_model->channel_length - ptr->channel_model->channel_offset; + timestamp_to_free -= (ptr->channel_model->channel_length - 1) + ptr->channel_model->channel_offset; } clear_old_packets(ptr->received_packets, timestamp_to_free); } From 033aed736e8b2555180fb5221b52ff55c967134a Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 22 Oct 2025 21:08:17 +0200 Subject: [PATCH 35/43] Rfsim: Optimization for simple configurations - Added first beam optimization to combine_tx_buffers. - Added first beam & first peer optimization to rfsimulator_read_internal This results in significant speed up for the first rx beam, first tx beam and first peer which is the base case with beam simulation disabled. --- radio/rfsimulator/apply_channelmod.c | 2 +- radio/rfsimulator/rfsimulator.h | 2 +- radio/rfsimulator/simulator.cpp | 109 ++++++++++++++++++--------- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 2dac26c4e6..3f2e8d85c1 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -235,7 +235,7 @@ void update_channel_model(channel_desc_t *channelDesc, uint64_t TS) either we regenerate the channel (call again random_channel(desc,0)), or we keep it over subframes legacy: we regenerate each sub frame in UL, and each frame only in DL */ -void rxAddInput(const c16_t **input_sig, +void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, diff --git a/radio/rfsimulator/rfsimulator.h b/radio/rfsimulator/rfsimulator.h index 90c2be2f82..802a2a2e34 100644 --- a/radio/rfsimulator/rfsimulator.h +++ b/radio/rfsimulator/rfsimulator.h @@ -24,7 +24,7 @@ #ifndef __RFSIMULATOR_H #define __RFSIMULATOR_H #include -void rxAddInput(const c16_t **input_sig, +void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_desc_t *channelDesc, diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 793dfedf18..b4b4b12ea2 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1045,17 +1045,18 @@ static std::vector get_beam_ids(uint64_t beam_map) * @param num_aatx The number of antennas to process. * @param num_samples The number of samples to process. * @param rx_beam_id Receiver configured beam id for the timestamp + * @param samples Pointer to the output buffer where combined samples will be stored. * @return A vector of vectors containing the combined samples for each antenna. */ -static std::vector> combine_received_beams(rfsimulator_state_t *t, - std::queue &received_packets, - uint64_t start_timestamp, - int num_aatx, - size_t num_samples, - int rx_beam_id) +static void combine_received_beams(rfsimulator_state_t *t, + std::queue &received_packets, + uint64_t start_timestamp, + int num_aatx, + size_t num_samples, + int rx_beam_id, + c16_t **samples) { // Assume received_packets is ordered by timestamp - std::vector> ant_buffers(num_aatx, std::vector(num_samples, {0, 0})); std::queue packets_copy = received_packets; while (!packets_copy.empty()) { rfsim_packet_t *pkt = packets_copy.front(); @@ -1081,16 +1082,38 @@ static std::vector> combine_received_beams(rfsimulator_state_ for (int aatx = 0; aatx < num_aatx; aatx++) { c16_t *buffer = (c16_t *)pkt->payload; c16_t *tx_ant_buffer_in = &buffer[(num_aatx * beam + aatx) * pkt->header.size + read_start_idx]; - for (int s = write_start_idx; s < write_end_idx; s++) { - ant_buffers[aatx][s].r += tx_ant_buffer_in->r * gain_linear; - ant_buffers[aatx][s].i += tx_ant_buffer_in->i * gain_linear; - tx_ant_buffer_in++; + if (beam == 0) { + // For the first beam, we can directly copy the samples + if (gain_dB == 0.0f) { + // If gain is 0 dB, we can use memcpy for efficiency + memcpy(&samples[aatx][write_start_idx], tx_ant_buffer_in, (write_end_idx - write_start_idx) * sizeof(c16_t)); + } else { + for (int s = write_start_idx; s < write_end_idx; s++) { + samples[aatx][s].r = tx_ant_buffer_in->r * gain_linear; + samples[aatx][s].i = tx_ant_buffer_in->i * gain_linear; + tx_ant_buffer_in++; + } + } + } else { + // For subsequent beams, we need to ensure we accumulate the samples + if (gain_dB == 0.0f) { + for (int s = write_start_idx; s < write_end_idx; s++) { + samples[aatx][s].r += tx_ant_buffer_in->r; + samples[aatx][s].i += tx_ant_buffer_in->i; + tx_ant_buffer_in++; + } + } else { + for (int s = write_start_idx; s < write_end_idx; s++) { + samples[aatx][s].r += tx_ant_buffer_in->r * gain_linear; + samples[aatx][s].i += tx_ant_buffer_in->i * gain_linear; + tx_ant_buffer_in++; + } + } } } } packets_copy.pop(); } - return ant_buffers; } static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time) @@ -1164,11 +1187,13 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, openair0_timestamp timestamp, int nsamps, int nbAnt, - int rx_beam_id) + int rx_beam_id, + bool is_first_beam) { cf_t temp_array[nbAnt][nsamps]; bool channel_modelling = false; // Add all input nodes signal in the output buffer + bool is_first_peer = true; for (int sock = 0; sock < MAX_FD_RFSIMU; sock++) { buffer_t *ptr = &t->buf[sock]; @@ -1188,38 +1213,52 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, } const uint64_t channel_offset = ptr->channel_model->channel_offset; const uint64_t channel_length = ptr->channel_model->channel_length; - std::vector> ant_buffers = combine_received_beams(t, - ptr->received_packets, - timestamp - channel_offset - (channel_length - 1), - ptr->nbAnt, - nsamps + channel_length - 1, - rx_beam_id); - const c16_t *input[ant_buffers.size()]; + std::vector> ant_buffers(ptr->nbAnt, std::vector(nsamps + channel_length - 1, {0, 0})); + c16_t *input[ant_buffers.size()]; for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { input[aatx] = ant_buffers[aatx].data(); } + + combine_received_beams(t, + ptr->received_packets, + timestamp - channel_offset - (channel_length - 1), + ptr->nbAnt, + nsamps + channel_length - 1, + rx_beam_id, + input); + for (int aarx = 0; aarx < nbAnt; aarx++) { rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps, timestamp); } } else { - std::vector> ant_buffers = - combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id); - for (int aarx = 0; aarx < nbAnt; aarx++) { - double H_awgn_mimo_coeff[ant_buffers.size()]; - for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { - uint32_t ant_diff = std::abs(aatx - aarx); - H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; + if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1)) { + // optimization: The buffer is uninitialized so samples can be written directly in the buffer + combine_received_beams(t, ptr->received_packets, timestamp, 1, nsamps, rx_beam_id, samples); + } else { + std::vector> ant_buffers(ptr->nbAnt, std::vector(nsamps, {0, 0})); + c16_t *input[ant_buffers.size()]; + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + input[aatx] = ant_buffers[aatx].data(); } + combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id, input); + for (int aarx = 0; aarx < nbAnt; aarx++) { + double H_awgn_mimo_coeff[ant_buffers.size()]; + for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { + uint32_t ant_diff = std::abs(aatx - aarx); + H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0; + } - for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { // sum up signals from nbAnt_tx antennas - for (int i = 0; i < nsamps; i++) { // loop over nsamps - samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; - samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; - } // end for a_tx - } // end for i (number of samps) - } // end for a_rx + for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { + for (int i = 0; i < nsamps; i++) { + samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx]; + samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx]; + } + } + } + } } } + is_first_peer = false; } bool apply_global_noise = get_noise_power_dBFS() != INVALID_DBFS_VALUE; @@ -1347,7 +1386,7 @@ static int rfsimulator_read_beams(openair0_device *device, for (int i = 0; i < nbAnt; i++) { samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp; } - rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam]); + rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam], beam == 0); } timestamp += nsamps_beam_map; nsamps_to_process -= nsamps_beam_map; From b10617607aed6240823812139528cd07cbeca54b Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 23 Oct 2025 16:18:34 +0200 Subject: [PATCH 36/43] RFsim: Allow arbitrary order of beams. Allow arbitrary order of beam ids in set_beams2 function. This allows the application to select any arbitrary order of beam ids. The underlying transport mechanism still uses beam_map so the beam_ids are ordered before sending. Also replaced tx_beam_map/rx_beam_map into beam_map. Also fixed an issue where --rfsimulator.enable_beams required an argument. --- radio/COMMON/common_lib.h | 16 +++++ radio/rfsimulator/simulator.cpp | 116 +++++++++++++++++++++----------- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/radio/COMMON/common_lib.h b/radio/COMMON/common_lib.h index cb1b0c8ddc..5705b9de45 100644 --- a/radio/COMMON/common_lib.h +++ b/radio/COMMON/common_lib.h @@ -623,6 +623,22 @@ struct openair0_device_t { */ int (*trx_set_beams)(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp); + /*! \brief Set tx/rx beams + * + * Set the tx/rx beams. This has to be done in advance of the reception in order to + * allow the underlying device to change receiver configuration. The exact time depends + * on the device. + * + * NOTICE: the samples returned from trx_read_func may belong to more than one beam. It is up + * to the application to determine the beam of the received samples. + * + * \param device the hardware to use + * \param beams pointer to array of beam ids + * \param num_beams number of beams + * \return 0 on success + */ + int (*trx_set_beams2)(openair0_device *device, int* beams, int num_beams, openair0_timestamp timestamp); + /*! \brief RRU Configuration callback * \param idx RU index * \param arg pointer to capabilities or configuration diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index b4b4b12ea2..0ec98f1a4d 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -30,6 +30,7 @@ #include "PHY/TOOLS/tools_defs.h" #include "PHY/defs_common.h" #include "utils.h" +#include #include #include #include @@ -59,6 +60,7 @@ extern "C" { #include #include #include +#include #define PORT 4043 // default TCP port for this simulator #define sampleToByte(a, b) ((a) * (b) * sizeof(sample_t)) @@ -84,6 +86,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; " saviq: enable saving written iqs to a file\n" #define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED +#define simBool PARAMFLAG_BOOL | PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED // clang-format off /*----------------------------------------------------------------------------------------------------------------------------------------------------*/ /* configuration parameters for the rfsimulator device */ @@ -100,10 +103,9 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; UINT64PARAM("offset", "\n", simOpt, &rfsimulator->chan_offset, 0L), \ DOUBLEPARAM("prop_delay", "\n", simOpt, &(rfsimulator->prop_delay_ms), 0.0), \ INTPARAM("wait_timeout", "\n", simOpt, &rfsimulator->wait_timeout, 1), \ - BOOLPARAM("enable_beams", "\n", simOpt, &(beam_ctrl->enable_beams), 0), \ + BOOLPARAM("enable_beams", "\n", simBool,&(beam_ctrl->enable_beams), 0), \ INTPARAM("num_concurrent_beams", "\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \ - UINT64PARAM("tx_beam_map", "\n", simOpt, &beam_ctrl->tx.beam_map, 1), \ - UINT64PARAM("rx_beam_map", "\n", simOpt, &beam_ctrl->rx.beam_map, 1), \ + UINT64PARAM("beam_map", "\n", simOpt, &beam_map, 1), \ STRINGPARAM(RFSIMU_BEAM_GAINS, "\n", simOpt, NULL, NULL), \ }; // clang-format on @@ -134,12 +136,12 @@ static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}}; typedef c16_t sample_t; // 2*16 bits complex number typedef struct beam_switch_command_t { - uint64_t beam_map; + std::vector beams; openair0_timestamp timestamp; } beam_switch_command_t; typedef struct { - uint64_t beam_map; + std::vector beams; std::queue cmd_queue; std::mutex mutex; } beam_state_t; @@ -212,10 +214,10 @@ typedef struct { * @param nsamps_out output pointer to receive the number of samples until the next beam switch. * @return The beam map (uint64_t) valid for the given timestamp. */ -static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out) +static std::vector get_beams(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out) { std::lock_guard lock(beam_state->mutex); - uint64_t current_map = beam_state->beam_map; + std::vector current_beams = beam_state->beams; uint32_t samples_to_next_switch = nsamps; // Find the latest beam_switch_command_t with timestamp <= requested timestamp @@ -225,7 +227,7 @@ static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timest while (!queue_copy.empty()) { const beam_switch_command_t &cmd = queue_copy.front(); if (cmd.timestamp <= timestamp) { - current_map = cmd.beam_map; + current_beams = cmd.beams; queue_copy.pop(); } else { samples_to_next_switch = (cmd.timestamp > timestamp) ? (cmd.timestamp - timestamp) : nsamps; @@ -240,9 +242,33 @@ static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timest *nsamps_out = samples_to_next_switch; - return current_map; + return current_beams; } + +static std::vector beam_map_to_beams(uint64_t beam_map) +{ + int num_beams = __builtin_popcountll(beam_map); + AssertFatal(num_beams > 0, "Needs at least one beam\n"); + std::vector beam_ids; + for (int i = 0; i < MAX_BEAMS; i++) { + if (beam_map & (1ULL << i)) { + beam_ids.push_back(i); + } + } + return beam_ids; +} + +static uint64_t beams_to_beam_map(const std::vector &beam_ids) +{ + uint64_t beam_map = 0; + for (size_t i = 0; i < beam_ids.size(); i++) { + beam_map |= (1ULL << beam_ids[i]); + } + return beam_map; +} + + /** * @brief Clears outdated beam switch commands from the queue and updates the current beam map. * @@ -259,7 +285,7 @@ static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timest std::lock_guard lock(beam_state->mutex); while (!beam_state->cmd_queue.empty()) { if (beam_state->cmd_queue.front().timestamp <= timestamp) { - beam_state->beam_map = beam_state->cmd_queue.front().beam_map; + beam_state->beams = beam_state->cmd_queue.front().beams; beam_state->cmd_queue.pop(); } else { break; @@ -448,7 +474,19 @@ static int rfsimulator_set_beams(openair0_device *device, uint64_t beam_map, ope rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl; std::lock_guard lock_tx(beam_ctrl->tx.mutex); std::lock_guard lock_rx(beam_ctrl->rx.mutex); - beam_switch_command_t command = {.beam_map = beam_map, .timestamp = timestamp}; + beam_switch_command_t command = {.beams = beam_map_to_beams(beam_map), .timestamp = timestamp}; + beam_ctrl->rx.cmd_queue.emplace(command); + beam_ctrl->tx.cmd_queue.emplace(command); + return 0; +} + +static int rfsimulator_set_beams_vector(openair0_device *device, int* beams, int num_beams, openair0_timestamp timestamp) +{ + rfsimulator_state_t *s = static_cast(device->priv); + rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl; + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard lock_rx(beam_ctrl->rx.mutex); + beam_switch_command_t command = {.beams = std::vector(beams, beams + num_beams), .timestamp = timestamp}; beam_ctrl->rx.cmd_queue.emplace(command); beam_ctrl->tx.cmd_queue.emplace(command); return 0; @@ -481,6 +519,7 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) char *saveF = NULL; char *modelname = NULL; rfsim_beam_ctrl_t *beam_ctrl = rfsimulator->beam_ctrl; + uint64_t beam_map = 1; paramdef_t rfsimu_params[] = RFSIMULATOR_PARAMS_DESC; int p = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_OPTIONS_PARAMNAME); int ret = config_get(config_get_if(), rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_SECTION); @@ -519,6 +558,10 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) process_gains(*rfsimu_params[beam_gains_param_index].strptr, beam_ctrl); } + std::vector initial_beams = beam_map_to_beams(beam_map); + beam_ctrl->rx.beams = initial_beams; + beam_ctrl->tx.beams = initial_beams; + if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0) rfsimulator->role = SIMU_ROLE_SERVER; else @@ -533,8 +576,8 @@ static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void uint64_t beam_map = strtoull(buff, NULL, 0); std::lock_guard lock_tx(beam_ctrl->tx.mutex); std::lock_guard lock_rx(beam_ctrl->rx.mutex); - beam_ctrl->rx.beam_map = beam_map; - beam_ctrl->tx.beam_map = beam_map; + beam_ctrl->rx.beams = beam_map_to_beams(beam_map); + beam_ctrl->tx.beams = beam_map_to_beams(beam_map); prnt("Beam map set to 0x%lx\n", beam_map); return CMDSTATUS_FOUND; } @@ -854,7 +897,7 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, void ***samplesVoid, int nsamps, int nbAnt, - uint64_t beam_map, + std::vector tx_beams, int flags) { mutexlock(t->Sockmutex); @@ -864,15 +907,18 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t, buffer_t *b = &t->buf[i]; if (b->conn_sock >= 0) { - if (!nbAnt) - LOG_E(HW, "rfsimulator sending 0 tx antennas\n"); - samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beam_map}; + samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beams_to_beam_map(tx_beams)}; fullwrite(b->conn_sock, &header, sizeof(header), t); - int num_beams = __builtin_popcountll(beam_map); + int num_beams = tx_beams.size(); + // Send beams in order of beam index. This is required for beam_map to work correctly on the receiver side. + std::vector indices(tx_beams.size()); + std::iota(indices.begin(), indices.end(), 0); + std::sort(indices.begin(), indices.end(), [&](size_t i, size_t j) { return tx_beams[i] < tx_beams[j]; }); + AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n"); for (int beam = 0; beam < num_beams; beam++) { for (int a = 0; a < nbAnt; a++) { - sample_t *in = (sample_t *)samplesVoid[beam][a]; + sample_t *in = (sample_t *)samplesVoid[indices[beam]][a]; fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t); } } @@ -926,8 +972,8 @@ static int rfsimulator_write_beams(openair0_device *device, } while (nsamps > 0) { uint32_t nsamps_beam_map; - uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map); - rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beam_map, flags); + std::vector beams = get_beams(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map); + rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beams, flags); for (int beam = 0; beam < num_beams; beam++) { for (int aatx = 0; aatx < nbAnt; aatx++) { char *ptr = (char *)samples_ptr[beam][aatx]; @@ -1018,19 +1064,6 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_ return; } -static std::vector get_beam_ids(uint64_t beam_map) -{ - int num_beams = __builtin_popcountll(beam_map); - AssertFatal(num_beams > 0, "Needs at least one beam\n"); - std::vector beam_ids; - for (int i = 0; i < MAX_BEAMS; i++) { - if (beam_map & (1ULL << i)) { - beam_ids.push_back(i); - } - } - return beam_ids; -} - /** * @brief Combines samples from received packets into a single buffer for the rx beam * @@ -1070,7 +1103,8 @@ static void combine_received_beams(rfsimulator_state_t *t, break; } - std::vector tx_beams = get_beam_ids(pkt->header.beam_map); + // The beams transmitted in are ordered by beam index + std::vector tx_beams = beam_map_to_beams(pkt->header.beam_map); for (uint beam = 0; beam < tx_beams.size(); beam++) { float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beams[beam]); float gain_linear = powf(10, gain_dB / 20.0); @@ -1373,20 +1407,19 @@ static int rfsimulator_read_beams(openair0_device *device, int nsamps_to_process = nsamps; while (nsamps_to_process > 0) { uint32_t nsamps_beam_map; - uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map); - std::vector rx_beam_ids = get_beam_ids(beam_map); - if ((int)rx_beam_ids.size() != num_beams) { + std::vector rx_beams = get_beams(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map); + if ((int)rx_beams.size() != num_beams) { LOG_D(HW, "Number of beams does not match application request num_beams %d, beam_map beams %lu\n", num_beams, - rx_beam_ids.size()); + rx_beams.size()); } - for (int beam = 0; beam < num_beams && beam < (int)rx_beam_ids.size(); beam++) { + for (int beam = 0; beam < num_beams && beam < (int)rx_beams.size(); beam++) { c16_t *samples_beam[nbAnt]; for (int i = 0; i < nbAnt; i++) { samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp; } - rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam], beam == 0); + rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beams[beam], beam == 0); } timestamp += nsamps_beam_map; nsamps_to_process -= nsamps_beam_map; @@ -1526,6 +1559,7 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d device->priv = rfsimulator; device->trx_write_init = rfsimulator_write_init; device->trx_set_beams = rfsimulator_set_beams; + device->trx_set_beams2 = rfsimulator_set_beams_vector; for (int i = 0; i < MAX_FD_RFSIMU; i++) rfsimulator->buf[i].conn_sock = -1; From d6da7a5605a99e536a381a50c2ccbda4e3b03c67 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Sat, 25 Oct 2025 22:20:11 +0200 Subject: [PATCH 37/43] vrtsim: add stubs to beam functions Add stubbed versions of _beam_ apis to vrtsim. --- radio/vrtsim/vrtsim.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 9e678a5305..37f1f1a382 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -519,6 +519,18 @@ static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, v : vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); } +static int vrtsim_write_beams(openair0_device *device, + openair0_timestamp timestamp, + void ***buff, + int nsamps, + int nb_antennas_tx, + int num_beams, + int flags) +{ + vrtsim_write(device, timestamp, (void **)buff[0], nsamps, nb_antennas_tx, flags); + return nsamps; +} + static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt) { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; @@ -628,6 +640,16 @@ static int vrtsim_set_freq(openair0_device *device, openair0_config_t *openair0_ return 0; } +static int vrtsim_set_beams(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp) +{ + return 0; +} + +static int vrtsim_set_beams2(openair0_device *device, int *beam_ids, int num_beams, openair0_timestamp timestamp) +{ + return 0; +} + __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { vrtsim_state_t *vrtsim_state = calloc_or_fail(1, sizeof(vrtsim_state_t)); @@ -643,6 +665,9 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi device->trx_set_gains_func = vrtsim_stub2; device->trx_write_func = vrtsim_write; device->trx_read_func = vrtsim_read; + device->trx_write_beams_func = vrtsim_write_beams; + device->trx_set_beams = vrtsim_set_beams; + device->trx_set_beams2 = vrtsim_set_beams2; device->type = RFSIMULATOR; device->openair0_cfg = &openair0_cfg[0]; From d6bd2d295fac86f773039422b4580d9f9e603ab4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Wed, 29 Oct 2025 08:06:30 +0100 Subject: [PATCH 38/43] Add beam section to RFSimulator README.md --- radio/rfsimulator/README.md | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/radio/rfsimulator/README.md b/radio/rfsimulator/README.md index 40688b755a..3b4c91d61d 100644 --- a/radio/rfsimulator/README.md +++ b/radio/rfsimulator/README.md @@ -209,3 +209,54 @@ to reduce the amount of transported samples: A possible, unimplemented optimization would be to compress samples. You can further [tune your machine](../../doc/tuning_and_security.md) + +# Beam simulation + +RFsimulator supports beam domain simulation. + +## Configuration + +Several new CLI parameters were added + +* `--rfsimulator.enable_beams` : enable beam domain simulation. Should match on server and all clients. +* `--rfsimulator.beam_gains ` : define a matrix of additional pathloss values (in dB) +to simulate the effect of different beam combinations in the RF simulator. You provide a comma-separated +list of numbers, which forms the first row of a square matrix. The rest of the matrix is filled by projecting +these values onto the diagonals, making it symmetric. + + Example list: `0,-2,-3` + Resulting matrix: + ``` + [[0,-2,-3], + [-2,0,-2], + [-3,-2,0]] + ``` + + The beam gain matrix will be used during beam combining. RX beam selects the row and TX beam selects column. + Using the example above, if gNB is receiving in beam 1 and the UE is transmitting in beam 1, an additional + 2 dB pathloss will be applied on top of the pathloss from the channel model (if present). + +* `--rfsimulator.beam_map ` : where `` is a `uint64_t` value where each bit is an enabled TX/RX beam. + For gNB: Initial beam_map, i.e. which beams gNB transmits/receives before calling beam APIs. + For UE: Beam position in beam space in the simulation. The UE is not expected to use the beam APIs for now. + +## Runtime commands + +### Moving the UE in beam space + +Use telnet command `rfsimu setbeams `. It works similar to the CLI command `--rfsimulator.beam_map `. +Suggest to only use single bits here + +### Modifying the gNB beam + +It is possible to test the beam domain simulation without implementing the beam APIs. The same telnet command can be used +to modify the tx/rx beam of the gNB. The gNB does not need to be beam-aware and use the new APIs. + +## Programming guide + +RFsimulator is attempting to simulate hardware device operation, but there are differences. Like with real hardware, +it is expected that you provide the configured beams in `set_beams` function ahead of sample reception. This is due +to the fact that a hardware device will buffer received samples before the driver requests them. + +For rfsimulator, as long as the call to `set_beams` with timestamp `n` is done before `trx_read` which is expected +to return sample `n` the beam switch command will apply to the received samples. From 56970390f241ff65146ff0fdfc21623234f5e315 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 30 Oct 2025 11:42:21 +0100 Subject: [PATCH 39/43] RFsim: handle additional channel offset from CLI Handle extra channel offset coming from CLI parameter prop_delay. --- radio/rfsimulator/simulator.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 0ec98f1a4d..c42f216a47 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1267,14 +1267,14 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t, } else { if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1)) { // optimization: The buffer is uninitialized so samples can be written directly in the buffer - combine_received_beams(t, ptr->received_packets, timestamp, 1, nsamps, rx_beam_id, samples); + combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, 1, nsamps, rx_beam_id, samples); } else { std::vector> ant_buffers(ptr->nbAnt, std::vector(nsamps, {0, 0})); c16_t *input[ant_buffers.size()]; for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) { input[aatx] = ant_buffers[aatx].data(); } - combine_received_beams(t, ptr->received_packets, timestamp, ptr->nbAnt, nsamps, rx_beam_id, input); + combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, ptr->nbAnt, nsamps, rx_beam_id, input); for (int aarx = 0; aarx < nbAnt; aarx++) { double H_awgn_mimo_coeff[ant_buffers.size()]; for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) { @@ -1455,7 +1455,10 @@ static int rfsimulator_read_beams(openair0_device *device, if (ptr->conn_sock != -1 && !ptr->received_packets.empty()) { openair0_timestamp timestamp_to_free = t->nextRxTstamp - 1; if (ptr->channel_model) { - timestamp_to_free -= (ptr->channel_model->channel_length - 1) + ptr->channel_model->channel_offset; + timestamp_to_free -= + (ptr->channel_model->channel_length - 1) + std::max(ptr->channel_model->channel_offset, t->chan_offset); + } else { + timestamp_to_free -= t->chan_offset; } clear_old_packets(ptr->received_packets, timestamp_to_free); } From 8a36b9b85baadef61600669a7c498e1583057545 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 3 Nov 2025 21:22:48 +0100 Subject: [PATCH 40/43] rfsimulator: fix beam_ctrl_t leak. --- radio/rfsimulator/simulator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index c42f216a47..306d24084c 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1490,6 +1490,7 @@ static void rfsimulator_end(openair0_device *device) } clear_beam_queue(&s->beam_ctrl->tx, INT64_MAX); clear_beam_queue(&s->beam_ctrl->rx, INT64_MAX); + delete s->beam_ctrl; close(s->epollfd); free(s); } From ac97ba4e645ab9e939fce3e0f8e6e34a1f5998e4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 4 Nov 2025 16:43:16 +0100 Subject: [PATCH 41/43] RFsim: Disable beam API if beam simulation is disabled --- radio/rfsimulator/simulator.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index 306d24084c..a1cf97bbf6 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -1553,9 +1553,11 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d device->trx_set_freq_func = rfsimulator_set_freq; device->trx_set_gains_func = rfsimulator_set_gains; device->trx_write_func = rfsimulator_write; - device->trx_write_beams_func = rfsimulator_write_beams; device->trx_read_func = rfsimulator_read; - device->trx_read_beams_func = rfsimulator_read_beams; + if (rfsimulator->beam_ctrl->enable_beams) { + device->trx_write_beams_func = rfsimulator_write_beams; + device->trx_read_beams_func = rfsimulator_read_beams; + } /* let's pretend to be a b2x0 */ device->type = RFSIMULATOR; openair0_cfg[0].rx_gain[0] = 0; From ade7901fb26a102e6e7367fc772e648050c2e905 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 27 Nov 2025 12:15:17 +0100 Subject: [PATCH 42/43] RFSim: Add beamids list based user interface for beam domain simulation Add CLI parameter rfsimulator.beam_ids which uses a list of beam ids instead of a beam map. The beam map API is still present and can be used interchangably. Add telnet command setbeamids which has similar semantics --- radio/rfsimulator/README.md | 5 +++-- radio/rfsimulator/simulator.cpp | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/radio/rfsimulator/README.md b/radio/rfsimulator/README.md index 3b4c91d61d..298c7ecfda 100644 --- a/radio/rfsimulator/README.md +++ b/radio/rfsimulator/README.md @@ -239,13 +239,14 @@ these values onto the diagonals, making it symmetric. * `--rfsimulator.beam_map ` : where `` is a `uint64_t` value where each bit is an enabled TX/RX beam. For gNB: Initial beam_map, i.e. which beams gNB transmits/receives before calling beam APIs. For UE: Beam position in beam space in the simulation. The UE is not expected to use the beam APIs for now. +* `--rfsimulator.beam_ids ` : where `` is a comma separated list. Same as above but added for convenience. ## Runtime commands ### Moving the UE in beam space -Use telnet command `rfsimu setbeams `. It works similar to the CLI command `--rfsimulator.beam_map `. -Suggest to only use single bits here +Use telnet command `rfsimu setbeams ` or `rfsimu setbeamids `. They correspond to the CLI parameters described +above and work the same way. ### Modifying the gNB beam diff --git a/radio/rfsimulator/simulator.cpp b/radio/rfsimulator/simulator.cpp index a1cf97bbf6..a29001e0bf 100644 --- a/radio/rfsimulator/simulator.cpp +++ b/radio/rfsimulator/simulator.cpp @@ -79,6 +79,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; #define RFSIMU_SECTION "rfsimulator" #define RFSIMU_OPTIONS_PARAMNAME "options" #define RFSIMU_BEAM_GAINS "beam_gains" +#define RFSIMU_BEAM_IDS "beam_ids" #define RFSIM_CONFIG_HELP_OPTIONS \ " list of comma separated options to enable rf simulator functionalities. Available options: \n" \ @@ -106,6 +107,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; BOOLPARAM("enable_beams", "\n", simBool,&(beam_ctrl->enable_beams), 0), \ INTPARAM("num_concurrent_beams", "\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \ UINT64PARAM("beam_map", "\n", simOpt, &beam_map, 1), \ + STRINGPARAM(RFSIMU_BEAM_IDS, "\n", simOpt, NULL, NULL), \ STRINGPARAM(RFSIMU_BEAM_GAINS, "\n", simOpt, NULL, NULL), \ }; // clang-format on @@ -119,6 +121,7 @@ static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt static int rfsimu_getdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_vtime_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg); static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void *arg); +static int rfsimu_set_beamids(char *buff, int debug, telnet_printfunc_t prnt, void *arg); // clang-format off static telnetshell_cmddef_t rfsimu_cmdarray[] = { {"show models", "", (cmdfunc_t)rfsimu_setchanmod_cmd, {(webfunc_t)getset_currentchannels_type}, TELNETSRV_CMDFLAG_WEBSRVONLY | TELNETSRV_CMDFLAG_GETWEBTBLDATA, NULL}, @@ -127,6 +130,7 @@ static telnetshell_cmddef_t rfsimu_cmdarray[] = { {"getdistance", "", (cmdfunc_t)rfsimu_getdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, {"vtime", "", (cmdfunc_t)rfsimu_vtime_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_AUTOUPDATE}, {"setbeam", "beam_map", (cmdfunc_t)rfsimu_set_beam, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, + {"setbeamids", "beam_id1,beam_id2,...", (cmdfunc_t)rfsimu_set_beamids, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ}, {"", "", NULL}, }; // clang-format on @@ -562,6 +566,18 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator) beam_ctrl->rx.beams = initial_beams; beam_ctrl->tx.beams = initial_beams; + int beam_ids_param_index = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_BEAM_IDS); + if (rfsimu_params[beam_ids_param_index].strptr) { + std::vector beam_ids; + std::stringstream ss(*rfsimu_params[beam_ids_param_index].strptr); + std::string token; + while (std::getline(ss, token, ',')) { + beam_ids.push_back(std::stoi(token)); + } + beam_ctrl->rx.beams = beam_ids; + beam_ctrl->tx.beams = beam_ids; + } + if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0) rfsimulator->role = SIMU_ROLE_SERVER; else @@ -582,6 +598,25 @@ static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void return CMDSTATUS_FOUND; } +static int rfsimu_set_beamids(char *buff, int debug, telnet_printfunc_t prnt, void *arg) +{ + rfsimulator_state_t *t = (rfsimulator_state_t *)arg; + rfsim_beam_ctrl_t *beam_ctrl = t->beam_ctrl; + AssertFatal(beam_ctrl->enable_beams, "Beam simualtion is disabled, cannot set beams\n"); + std::vector beam_ids; + std::stringstream ss(buff); + std::string token; + while (std::getline(ss, token, ',')) { + beam_ids.push_back(std::stoi(token)); + } + std::lock_guard lock_tx(beam_ctrl->tx.mutex); + std::lock_guard lock_rx(beam_ctrl->rx.mutex); + beam_ctrl->rx.beams = beam_ids; + beam_ctrl->tx.beams = beam_ids; + prnt("Beam ids set\n"); + return CMDSTATUS_FOUND; +} + static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg) { char *modelname = NULL; From 2a3755441b416c839c0a10277b184b296dd77b27 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Tue, 23 Dec 2025 11:57:55 +0100 Subject: [PATCH 43/43] Add missing CMakeLists entries for test_nr_frame_params --- openair1/PHY/INIT/tests/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openair1/PHY/INIT/tests/CMakeLists.txt b/openair1/PHY/INIT/tests/CMakeLists.txt index 9f41e0fec0..6c532ebd99 100644 --- a/openair1/PHY/INIT/tests/CMakeLists.txt +++ b/openair1/PHY/INIT/tests/CMakeLists.txt @@ -1,2 +1,4 @@ add_executable(test_nr_frame_params test_nr_frame_params.cpp) target_link_libraries(test_nr_frame_params PRIVATE LOG minimal_lib PHY_NR -lm GTest::gtest) +add_dependencies(tests test_nr_frame_params) +add_test(NAME test_nr_frame_params COMMAND ./test_nr_frame_params)