/* * SPDX-License-Identifier: LicenseRef-CSSL-1.0 */ /*! * \brief common APIs for different RF frontend device */ #include #include #include #include #include #include #include #include "common_lib.h" #include "assertions.h" #include "common/utils/load_module_shlib.h" #include "common/utils/LOG/log.h" #include "executables/softmodem-common.h" #include "common/config/config_paramdesc.h" #include "common/config/config_userapi.h" #include "common/cmake_defs.h" #define MAX_GAP 100ULL const char *const devtype_names[MAX_RF_DEV_TYPE] = {"", "USRP B200", "USRP X300", "USRP N300", "USRP X400", "BLADERF", "LMSSDR", "IRIS", "No HW", "UEDv2", "RFSIMULATOR"}; const char *get_devname(int devtype) { if (devtype < MAX_RF_DEV_TYPE && devtype !=MIN_RF_DEV_TYPE ) return devtype_names[devtype]; return "none"; } int set_device(openair0_device_t *device) { const char *devname = get_devname(device->type); if (strcmp(devname,"none") != 0) { LOG_I(HW,"[%s] has loaded %s device.\n",((device->host_type == RAU_HOST) ? "RAU": "RRU"),devname); } else { LOG_E(HW,"[%s] invalid HW device.\n",((device->host_type == RAU_HOST) ? "RAU": "RRU")); return -1; } return 0; } int set_transport(openair0_device_t *device) { switch (device->transp_type) { case ETHERNET_TP: LOG_I(HW,"[%s] has loaded ETHERNET trasport protocol.\n",((device->host_type == RAU_HOST) ? "RAU": "RRU")); return 0; break; case NONE_TP: LOG_I(HW,"[%s] has not loaded a transport protocol.\n",((device->host_type == RAU_HOST) ? "RAU": "RRU")); return 0; break; default: LOG_E(HW,"[%s] invalid transport protocol.\n",((device->host_type == RAU_HOST) ? "RAU": "RRU")); return -1; break; } } typedef int (*devfunc_t)(openair0_device_t *, openair0_config_t *, eth_params_t *); /* look for the interface library and load it */ int load_lib(openair0_device_t *device, openair0_config_t *openair0_cfg, eth_params_t *cfg, uint8_t flag) { loader_shlibfunc_t shlib_fdesc[1]; int ret=0; char *deflibname=OAI_RF_LIBNAME; openair0_cfg->command_line_sample_advance = get_softmodem_params()->command_line_sample_advance; openair0_cfg->recplay_mode = read_recplayconfig(&(openair0_cfg->recplay_conf),&(device->recplay_state)); if (openair0_cfg->recplay_mode == RECPLAY_RECORDMODE) { IS_SOFTMODEM_IQRECORDER = true; // softmodem has to know we use the iqrecorder to workaround randomized algorithms } if (openair0_cfg->recplay_mode == RECPLAY_REPLAYMODE) { deflibname=OAI_IQPLAYER_LIBNAME; shlib_fdesc[0].fname="device_init"; IS_SOFTMODEM_IQPLAYER = true; // softmodem has to know we use the iqplayer to workaround randomized algorithms } else if (IS_SOFTMODEM_RFSIM && flag == RAU_LOCAL_RADIO_HEAD) { deflibname=OAI_RFSIM_LIBNAME; shlib_fdesc[0].fname="device_init"; } else if (flag == RAU_LOCAL_RADIO_HEAD) { if (IS_SOFTMODEM_RFSIM) deflibname="rfsimulator"; else deflibname=OAI_RF_LIBNAME; shlib_fdesc[0].fname="device_init"; } else if (flag == RAU_REMOTE_THIRDPARTY_RADIO_HEAD) { deflibname=OAI_THIRDPARTY_TP_LIBNAME; shlib_fdesc[0].fname="transport_init"; } else { deflibname=OAI_TP_LIBNAME; shlib_fdesc[0].fname="transport_init"; } char *devname=NULL; paramdef_t device_params[]=DEVICE_PARAMS_DESC ; int numparams = sizeofArray(device_params); int devname_pidx = config_paramidx_fromname(device_params,numparams, CONFIG_DEVICEOPT_NAME); device_params[devname_pidx].defstrval=deflibname; config_get(config_get_if(), device_params, numparams, DEVICE_SECTION); ret=load_module_shlib(devname,shlib_fdesc,1,NULL); AssertFatal( (ret >= 0), "Library %s couldn't be loaded\n",devname); return ((devfunc_t)shlib_fdesc[0].fptr)(device,openair0_cfg,cfg); } int openair0_device_load(openair0_device_t *device, openair0_config_t *openair0_cfg) { int rc=0; rc=load_lib(device, openair0_cfg, NULL,RAU_LOCAL_RADIO_HEAD ); if ( rc >= 0) { if ( set_device(device) < 0) { LOG_E(HW, "%s %d:Unsupported radio head\n", __FILE__, __LINE__); return -1; } } else AssertFatal(false, "can't open the radio device: %s\n", get_devname(device->type)); pthread_mutex_init(&device->reOrder.mutex_store, NULL); pthread_mutex_init(&device->reOrder.mutex_write, NULL); return rc; } int openair0_transport_load(openair0_device_t *device, openair0_config_t *openair0_cfg, eth_params_t *eth_params) { int rc; rc=load_lib(device, openair0_cfg, eth_params, RAU_REMOTE_RADIO_HEAD); if ( rc >= 0) { if ( set_transport(device) < 0) { LOG_E(HW, "%s %d:Unsupported transport protocol\n", __FILE__, __LINE__); return -1; } } return rc; } static void writerEnqueue(re_order_t *ctx, openair0_timestamp_t timestamp, void **txp, int nsamps, int nbAnt, int flags) { pthread_mutex_lock(&ctx->mutex_store); LOG_D(HW, "Enqueue write for TS: %lu\n", timestamp); int i; for (i = 0; i < WRITE_QUEUE_SZ; i++) if (!ctx->queue[i].active) { ctx->queue[i].timestamp = timestamp; ctx->queue[i].active = true; ctx->queue[i].nsamps = nsamps; ctx->queue[i].nbAnt = nbAnt; ctx->queue[i].flags = flags; AssertFatal(nbAnt <= NB_ANTENNAS_TX, ""); for (int j = 0; j < nbAnt; j++) ctx->queue[i].txp[j] = txp[j]; break; } AssertFatal(i < WRITE_QUEUE_SZ, "Write queue full\n"); pthread_mutex_unlock(&ctx->mutex_store); } typedef struct PHY_VARS_NR_UE_s PHY_VARS_NR_UE; typedef int (*nrue_ru_write_t)(PHY_VARS_NR_UE *UE, openair0_timestamp_t timestamp, void **txp, int nsamps, int nbAnt, int flags); static void writerProcessWaitingQueue(nrue_ru_write_t nrue_ru_write, PHY_VARS_NR_UE *UE, openair0_device_t *device) { bool found = false; re_order_t *ctx = &device->reOrder; do { found = false; pthread_mutex_lock(&ctx->mutex_store); for (int i = 0; i < WRITE_QUEUE_SZ; i++) { if (ctx->queue[i].active && llabs(ctx->queue[i].timestamp - ctx->nextTS) < MAX_GAP) { openair0_timestamp_t timestamp = ctx->queue[i].timestamp; LOG_D(HW, "Dequeue write for TS: %lu\n", timestamp); int nsamps = ctx->queue[i].nsamps; int nbAnt = ctx->queue[i].nbAnt; int flags = ctx->queue[i].flags; void *txp[NB_ANTENNAS_TX]; AssertFatal(nbAnt <= NB_ANTENNAS_TX, ""); for (int j = 0; j < nbAnt; j++) txp[j] = ctx->queue[i].txp[j]; ctx->queue[i].active = false; pthread_mutex_unlock(&ctx->mutex_store); found = true; if (flags || IS_SOFTMODEM_RFSIM) { int wroteSamples; if (nrue_ru_write) wroteSamples = nrue_ru_write(UE, timestamp, txp, nsamps, nbAnt, flags); else wroteSamples = device->trx_write_func(device, timestamp, txp, nsamps, nbAnt, flags); if (wroteSamples != nsamps) LOG_W(HW, "Failed to write to RF: wrote %d out of %d samples\n", wroteSamples, nsamps); } ctx->nextTS = timestamp + nsamps; pthread_mutex_lock(&ctx->mutex_store); } } pthread_mutex_unlock(&ctx->mutex_store); } while (found); } // We assume the data behind *txp are permanently allocated // When we will go further, we can remove all RC.xxx.txdata buffers in xNB, in UE // but to make zerocopy and agnostic design, we need to make a proper ring buffer with mutex protection // mutex (or atomic flags) will be mandatory because this out order system root cause is there are several writer threads int openair0_write_reorder_common(nrue_ru_write_t nrue_ru_write, PHY_VARS_NR_UE *UE, openair0_device_t *device, openair0_timestamp_t timestamp, void **txp, int nsamps, int nbAnt, int flags) { int wroteSamples = 0; re_order_t *ctx = &device->reOrder; LOG_D(HW, "received write order ts: %lu, nb samples %d, next ts %luflags %d\n", timestamp, nsamps, timestamp + nsamps, flags); pthread_mutex_lock(&ctx->mutex_store); if (!ctx->initDone) { ctx->nextTS = timestamp; for (int i = 0; i < WRITE_QUEUE_SZ; i++) { ctx->queue[i].txp = malloc(sizeof(void *) * NB_ANTENNAS_TX); } ctx->initDone = true; } pthread_mutex_unlock(&ctx->mutex_store); if (pthread_mutex_trylock(&ctx->mutex_write) == 0) { // We have the write exclusivity if (llabs(timestamp - ctx->nextTS) < MAX_GAP) { // We are writing in sequence of the previous write if (flags || IS_SOFTMODEM_RFSIM) { if (nrue_ru_write) wroteSamples = nrue_ru_write(UE, timestamp, txp, nsamps, nbAnt, flags); else wroteSamples = device->trx_write_func(device, timestamp, txp, nsamps, nbAnt, flags); if (wroteSamples != nsamps) LOG_W(HW, "Failed to write to RF: wrote %d out of %d samples\n", wroteSamples, nsamps); } else wroteSamples = nsamps; ctx->nextTS = timestamp + nsamps; } else { writerEnqueue(ctx, timestamp, txp, nsamps, nbAnt, flags); } writerProcessWaitingQueue(nrue_ru_write, UE, device); pthread_mutex_unlock(&ctx->mutex_write); return wroteSamples ? wroteSamples : nsamps; } writerEnqueue(ctx, timestamp, txp, nsamps, nbAnt, flags); if (pthread_mutex_trylock(&ctx->mutex_write) == 0) { writerProcessWaitingQueue(nrue_ru_write, UE, device); pthread_mutex_unlock(&ctx->mutex_write); } return nsamps; } int openair0_write_reorder(openair0_device_t *device, openair0_timestamp_t timestamp, void **txp, int nsamps, int nbAnt, int flags) { return openair0_write_reorder_common(NULL, NULL, device, timestamp, txp, nsamps, nbAnt, flags); } void openair0_write_reorder_clear_context(openair0_device_t *device) { LOG_I(HW, "received write reorder clear context\n"); re_order_t *ctx = &device->reOrder; if (!ctx->initDone) return; if (pthread_mutex_trylock(&ctx->mutex_write) != 0) LOG_E(HW, "write_reorder_clear_context call while still writing on the device\n"); else pthread_mutex_unlock(&ctx->mutex_write); pthread_mutex_lock(&ctx->mutex_store); for (int i = 0; i < WRITE_QUEUE_SZ; i++) { ctx->queue[i].active = false; free(ctx->queue[i].txp); } ctx->initDone = false; pthread_mutex_unlock(&ctx->mutex_store); }