Compare commits

...

26 Commits

Author SHA1 Message Date
Laurent THOMAS
519d5b661a remove rx timestamp alignment to 32, add second barrier before tx: txHfull and tx < rx + max_ahead 2026-06-03 15:47:57 +02:00
Laurent THOMAS
21e407d2c9 improve threshold in signature test (rftest) 2026-05-30 15:21:22 +02:00
Laurent
d87d388546 add signature mode 2026-05-29 14:02:57 +02:00
Laurent
597a569c91 txHfull 2026-05-28 13:49:02 +02:00
Laurent THOMAS
00f3d711aa variable tx packet size 2026-05-20 15:18:10 +02:00
Laurent THOMAS
b251c36518 cleanup constants usage 2026-05-20 13:35:38 +02:00
Laurent THOMAS
407f19f3b3 cumulative modifications from test pc 2026-05-20 10:04:42 +02:00
Laurent THOMAS
85a6af5dd5 fix jesd204 and queue fixed samples advance 2026-05-19 15:29:33 +02:00
Laurent THOMAS
5a3a2ce5b3 better hole test case, set reasonnable constants 2026-05-19 15:10:02 +02:00
Laurent THOMAS
b454c08103 more tests, fake rx 2026-05-19 14:56:42 +02:00
Laurent
30b3c4f2d8 add ramp 2026-05-19 14:56:42 +02:00
Laurent
e3a2e557c7 add input file in rftest 2026-05-19 14:56:42 +02:00
Laurent
0acf8f56e3 add qam modulation on partial band in rftest 2026-05-19 14:56:42 +02:00
Laurent
ae5b22caad add qpsk modulation 2026-05-19 14:56:42 +02:00
Laurent
34593f34ec add QAM256 test 2026-05-19 14:56:42 +02:00
Laurent
d159864513 remove scaling in tx 2026-05-19 14:56:42 +02:00
Laurent THOMAS
183aafb42a fix-autoscale 2026-05-19 14:56:42 +02:00
Laurent THOMAS
1b567772c4 add command line options for calibration test tool 2026-05-19 14:56:42 +02:00
Laurent
8499ea9165 add papr 2026-05-19 14:56:42 +02:00
Laurent THOMAS
8fb2a7aaa3 add good real time option 2026-05-19 14:56:42 +02:00
Laurent THOMAS
bfec6910f2 first gnb running with P40 2026-05-19 14:56:42 +02:00
Laurent THOMAS
fea79b4163 make separate threads for rx and tx, control tx timing advance indepantly of the data source 2026-05-19 14:56:42 +02:00
laurent
de1e868eff enhance rftest scope 2026-05-19 14:56:42 +02:00
laurent
a358bdb601 first tx 2026-05-19 14:56:42 +02:00
laurent
a13f34d3d6 first trial 2026-05-19 14:56:42 +02:00
laurent
215941da12 dma latency 2 2026-05-19 14:56:42 +02:00
20 changed files with 1764 additions and 350 deletions

View File

@@ -1976,6 +1976,9 @@ target_link_libraries(nr-uesoftmodem PRIVATE
add_executable(rftest
${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_test.c
${OPENAIR_DIR}/openair1/PHY/TOOLS/calibration_scope.c
${OPENAIR_DIR}/radio/COMMON/common_lib.c
${OPENAIR1_DIR}/PHY/TOOLS/dfts_load.c
${OPENAIR_DIR}/executables/softmodem-common.c
)
target_link_libraries(rftest PRIVATE
minimal_lib PHY_NR_COMMON UTIL forms pthread dl m ${T_LIB}

View File

@@ -223,8 +223,8 @@ function main() {
shift 2;;
-w | --hardware)
case "$2" in
"USRP" | "BLADERF" | "LMSSDR" | "IRIS" | "ZMQ")
HWs+=" OAI_"$2
"USRP" | "BLADERF" | "LMSSDR" | "IRIS" | "ZMQ" | "OC")
HWs+=" OAI_$2"
TARGET_LIST="$TARGET_LIST oai_${2,,}devif" # ,, makes lowercase
CMAKE_CMD="$CMAKE_CMD -DOAI_$2=ON"
;;

View File

@@ -350,4 +350,7 @@ void lock_memory_to_ram(void)
int rc = mlockall(MCL_CURRENT | MCL_FUTURE);
if (rc != 0)
LOG_W(UTIL, "mlockall() failed: %d, %s\n", errno, strerror(errno));
int h=open("/dev/cpu_dma_latency", 0666);
int lat=2; // micro second
assert(sizeof(lat)==write(h,&lat,sizeof(lat)));
}

View File

@@ -131,7 +131,14 @@ void *L1_tx_thread(void *arg) {
PHY_VARS_gNB *gNB = (PHY_VARS_gNB*)arg;
while (oai_exit == 0) {
notifiedFIFO_elt_t *res = pullNotifiedFIFO(&gNB->L1_tx_out);
notifiedFIFO_elt_t *res = NULL;
do {
res = pullNotifiedFIFO(&gNB->L1_tx_out);
if (!res) {
LOG_W(HW, "possible underrun\n");
usleep(300);
}
} while (!res);
if (res == NULL) // stopping condition, happens only when queue is freed
break;
processingData_L1tx_t *info = (processingData_L1tx_t *)NotifiedFifoData(res);

View File

@@ -367,7 +367,7 @@ static void rx_rf(RU_t *ru, int *frame, int *slot)
uint32_t samples_per_slot_prev = get_samples_per_slot((*slot - 1) % fp->slots_per_frame, fp);
if (proc->timestamp_rx - old_ts != samples_per_slot_prev) {
LOG_D(PHY,
LOG_W(PHY,
"rx_rf: rfdevice timing drift of %" PRId64 " samples (ts_off %" PRId64 ")\n",
proc->timestamp_rx - old_ts - samples_per_slot_prev,
ru->ts_offset);

View File

@@ -41,8 +41,6 @@
#define SSS_START_IDX (3) /* [0:PSBCH 1:PSS0 2:PSS1 3:SSS0 4:SSS1] */
#define NUM_SSS_SYMBOLS (2)
#define SSS_METRIC_FLOOR_NR (30000)
void init_context_sss_nr(int amp);
void free_context_sss_nr(void);

View File

@@ -226,6 +226,7 @@ bool rx_sss_nr(const NR_DL_FRAME_PARMS *frame_parms,
/* Computation of signal with shift phase is based on below formula */
/* cosinus cos(x + y) = cos(x)cos(y) - sin(x)sin(y) */
/* sinus sin(x + y) = sin(x)cos(y) + cos(x)sin(y) */
#define SSS_METRIC_FLOOR_NR (7000)
int Nid1_start = 0;
int Nid1_end = N_ID_1_NUMBER;

View File

@@ -1,11 +1,8 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <stdlib.h>
#include "common/utils/system.h"
#include "common/utils/utils.h"
#include "common/utils/LOG/log.h"
#include <common/utils/LOG/log.h>
#include <common/utils/system.h>
#include <openair1/PHY/TOOLS/tools_defs.h>
#include <openair1/PHY/impl_defs_top.h>
#include "executables/softmodem-common.h"
#include "executables/nr-softmodem-common.h"
#include <forms.h>
@@ -21,12 +18,9 @@ typedef struct {
int16_t r;
int16_t i;
} scopeSample_t;
#define SquaredNorm(VaR) ((VaR).r*(VaR).r+(VaR).i*(VaR).i)
typedef struct {
void ** samplesRx;
openair0_device_t *rfdevice;
} calibData_t;
#define SquaredNorm(VaR) ((VaR).r * (VaR).r + (VaR).i * (VaR).i)
struct OAI_phy_scope_s;
typedef struct OAIgraph {
FL_OBJECT *graph;
FL_OBJECT *text;
@@ -42,16 +36,20 @@ typedef struct OAIgraph {
double *waterFallAvg;
bool initDone;
int iteration;
void (*funct) (struct OAIgraph *graph, calibData_t *);
void (*funct)(struct OAIgraph *graph,struct OAI_phy_scope_s * );
} OAIgraph_t;
/* Forms and Objects */
typedef struct {
calibData_t * context;
typedef struct OAI_phy_scope_s{
threads_t *context;
FL_FORM *phy_scope;
OAIgraph_t graph[20];
FL_OBJECT *button_0;
scopeSample_t *timeDomain;
scopeSample_t *timeDomainTx;
scopeSample_t *freqDomain;
scopeSample_t *freqDomainTx;
} OAI_phy_scope_t;
typedef struct {
@@ -104,35 +102,38 @@ static void commonGraph(OAIgraph_t *graph, int type, FL_Coord x, FL_Coord y, FL_
graph->iteration=0;
}
static OAIgraph_t calibrationCommonGraph( void (*funct) (OAIgraph_t *graph, calibData_t *context),
int type, FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h, const char *label, FL_COLOR pointColor)
static OAIgraph_t calibrationCommonGraph(void (*funct)(OAIgraph_t *graph,OAI_phy_scope_t * scope),
int type,
FL_Coord x,
FL_Coord y,
FL_Coord w,
FL_Coord h,
const char *label,
FL_COLOR pointColor)
{
OAIgraph_t graph = {0};
OAIgraph_t graph = {};
commonGraph(&graph, type, x, y, w, h, label, pointColor);
graph.funct=funct;
return graph;
}
static void setRange(OAIgraph_t *graph, float minX, float maxX, float minY, float maxY) {
if ( maxX > graph->maxX || minX < graph->minX ||
abs(maxX-graph->maxX)>abs(graph->maxX)/2 ||
abs(maxX-graph->maxX)>abs(graph->maxX)/2 ) {
if (maxX > graph->maxX || minX < graph->minX || fabs(maxX - graph->maxX) > fabs(graph->maxX) / 2
|| fabs(maxX - graph->maxX) > fabs(graph->maxX) / 2) {
graph->maxX/=2;
graph->minX/=2;
graph->maxX=max(graph->maxX,maxX);
graph->minX=min(graph->minX,minX);
fl_set_xyplot_xbounds(graph->graph, graph->minX*1.2, graph->maxX*1.2);
graph->maxX = fmax(graph->maxX, maxX);
graph->minX = fmin(graph->minX, minX);
fl_set_xyplot_xbounds(graph->graph, (int32_t)graph->minX * 1.2, (int32_t)graph->maxX * 1.2);
}
if ( maxY > graph->maxY || minY < graph->minY ||
abs(maxY-graph->maxY)>abs(graph->maxY)/2 ||
abs(maxY-graph->maxY)>abs(graph->maxY)/2 ) {
if (((maxY > graph->maxY) && (fabs(maxY - graph->maxY) > (fabs(graph->maxY) / 2)))
|| ((minY < graph->minY) && (fabs(minY - graph->minY) > (fabs(graph->minY) / 2)))) {
graph->maxY/=2;
graph->minY/=2;
graph->maxY=max(graph->maxY,maxY);
graph->minY=min(graph->minY,minY);
fl_set_xyplot_ybounds(graph->graph, graph->minY*1.2, graph->maxY*1.2);
graph->maxY = fmax(graph->maxY, maxY);
graph->minY = fmin(graph->minY, minY);
fl_set_xyplot_ybounds(graph->graph, (int32_t)graph->minY * 1.2, (int32_t)graph->maxY * 1.2);
}
}
@@ -166,19 +167,16 @@ static void oai_xygraph_getbuff(OAIgraph_t *graph, float **x, float **y, int len
*y=old_y;
}
static void oai_xygraph(OAIgraph_t *graph, float *x, float *y, int len, int layer, bool NoAutoScale) {
static void oai_xygraph(OAIgraph_t *graph, float *x, float *y, int len, int layer, int NoAutoScale) {
fl_redraw_object(graph->graph);
if ( NoAutoScale && graph->iteration%NoAutoScale == 0) {
float maxX=0, maxY=0, minX=0, minY=0;
for (int k=0; k<len; k++) {
maxX=max(maxX,x[k]);
minX=min(minX,x[k]);
maxY=max(maxY,y[k]);
minY=min(minY,y[k]);
}
setRange(graph, minX-5, maxX+5, minY-5, maxY+5);
}
@@ -254,29 +252,62 @@ static void genericPowerPerAntena(OAIgraph_t *graph, const int nb_ant, const sc
for (int i=0; i<len; i++) {
values[i] = SquaredNorm(data[ant][i]);
}
oai_xygraph(graph,time,values, len, ant, 10);
oai_xygraph(graph,time,values, len, ant, 1000);
}
}
}
static void gNBWaterFall (OAIgraph_t *graph, calibData_t *context) {
static void gNBWaterFall(OAIgraph_t *graph, OAI_phy_scope_t *scope)
{
//use 1st antenna
genericWaterFall(graph, (scopeSample_t *)context->samplesRx[0],
0, 0,
"X axis:one frame in time");
genericWaterFall(graph, scope->timeDomain, scope->context->dft_sz, 10, "X axis:one frame in time");
}
static void gNBfreqWaterFall (OAIgraph_t *graph, calibData_t *context) {
//use 1st antenna
genericWaterFall(graph, (scopeSample_t *)context->samplesRx[0],
0, 0,
"X axis:one frame in time");
static void spectrum(OAIgraph_t *graph, OAI_phy_scope_t *scope)
{
int len = scope->context->dft_sz;
for (int ri = 0; ri < 2; ri++) {
float *values;
float *time;
oai_xygraph_getbuff(graph, &time, &values, len, ri);
for (int i = 0; i < len; i++) {
values[i] = ri ? scope->freqDomain[(i + len / 2) % len].i : scope->freqDomain[(i + len / 2) % len].r;
}
oai_xygraph(graph, time, values, len, ri, 100);
}
}
static void zoomIn(OAIgraph_t *graph, OAI_phy_scope_t *scope)
{
/*
static time_t t = 0;
time_t n = time(NULL);
if (n == t)
return;
t = n;
*/
//int len = scope->context->dft_sz;
int detailLen = 750; //min(len, 1024);
//int beg=5500; max(0, rand()%len - detailLen )/2*2;
for (int ri = 0; ri < 2; ri++) {
float *values;
float *time;
oai_xygraph_getbuff(graph, &time, &values, detailLen, ri);
memset(values,0,sizeof(*values)*detailLen);
float *v=values;
//for (int j=2048*3-95; j < len-detailLen; j+=2048) {
int j=0;
for (int i = 0; i < detailLen; i++)
*v++ = ri ? scope->timeDomain[j+i].i : scope->timeDomain[j+i].r;
//}
oai_xygraph(graph,time, values, detailLen, ri, 10);
}
}
__attribute__((unused))
static void timeResponse (OAIgraph_t *graph, calibData_t *context)
static void timeResponse(OAIgraph_t *graph, threads_t *context)
{
#if 0
#if 0
const int len=2*phy_vars_gnb->frame_parms.ofdm_symbol_size;
float *values, *time;
oai_xygraph_getbuff(graph, &time, &values, len, 0);
@@ -290,93 +321,109 @@ static void timeResponse (OAIgraph_t *graph, calibData_t *context)
values[i] = SquaredNorm(data[i]);
}
oai_xygraph(graph,time,values, len, ue, 10);
oai_xygraph(graph,time,values, len/2, ue, 10);
}
}
#endif
#endif
}
static void puschIQ (OAIgraph_t *graph, calibData_t *context) {
#if 0
NR_DL_FRAME_PARMS *frame_parms=&phy_vars_gnb->frame_parms;
int sz=frame_parms->N_RB_UL*12*frame_parms->symbols_per_slot;
static void signalIQ(OAIgraph_t *graph,OAI_phy_scope_t *scope)
{
int len = scope->context->dft_sz;
float *I, *Q;
oai_xygraph_getbuff(graph, &I, &Q, len, 0);
for (int ue=0; ue<nb_UEs; ue++) {
scopeSample_t *pusch_comp = (scopeSample_t *) phy_vars_gnb->pusch_vars[ue]->rxdataF_comp[0];
float *I, *Q;
oai_xygraph_getbuff(graph, &I, &Q, sz, ue);
if (pusch_comp) {
for (int k=0; k<sz; k++ ) {
I[k] = pusch_comp[k].r;
Q[k] = pusch_comp[k].i;
}
oai_xygraph(graph,I,Q,sz,ue,10);
}
for (int k = 0; k < len; k++) {
I[k] = scope->freqDomain[k].r;
Q[k] = scope->freqDomain[k].i;
}
#endif
oai_xygraph(graph, I, Q, scope->context->dft_sz, 0, 100);
}
static void signalIQtx(OAIgraph_t *graph,OAI_phy_scope_t *scope)
{
int len = scope->context->dft_sz;
float *I, *Q;
oai_xygraph_getbuff(graph, &I, &Q, len, 0);
for (int k = 0; k < len; k++) {
I[k] = scope->freqDomainTx[k].r;
Q[k] = scope->freqDomainTx[k].i;
}
oai_xygraph(graph, I, Q, scope->context->dft_sz, 0, 100);
}
static OAI_phy_scope_t *createScopeCalibration(calibData_t * context) {
static OAI_phy_scope_t *createScopeCalibration(threads_t *context)
{
FL_OBJECT *obj;
OAI_phy_scope_t *fdui = calloc_or_fail((sizeof *fdui), 1);
fdui->context=context;
// Define form
fdui->phy_scope = fl_bgn_form( FL_NO_BOX, 800, 800 );
fdui->phy_scope = fl_bgn_form( FL_NO_BOX, 1200, 800 );
// This the whole UI box
obj = fl_add_box( FL_BORDER_BOX, 0, 0, 800, 800, "" );
obj = fl_add_box( FL_BORDER_BOX, 0, 0, 1200, 800, "" );
fl_set_object_color( obj, FL_BLACK, FL_WHITE );
int curY=0,x,y,w,h;
OAIgraph_t *graph = fdui->graph;
// Received signal
fdui->graph[0] = calibrationCommonGraph( gNBWaterFall, WATERFALL, 0, curY, 400, 100,
"Received Signal (Time-Domain, one frame)", FL_RED );
// Time-domain channel response
//fdui->graph[1] = calibrationCommonGraph( timeResponse, FL_NORMAL_XYPLOT, 410, curY, 400, 100, "SRS Frequency Response (samples, abs)", FL_RED );
*graph++ = calibrationCommonGraph(zoomIn, FL_NORMAL_XYPLOT, 0, curY, 1200, 100, "Received Signal in time zoom", FL_RED);
fl_get_object_bbox(fdui->graph[0].graph,&x, &y,&w, &h);
curY+=h;
// Frequency-domain channel response
fdui->graph[1] = calibrationCommonGraph( gNBfreqWaterFall, WATERFALL, 0, curY, 800, 100,
"Channel Frequency domain (RE, one frame)", FL_RED );
curY += h + 20;
// frequency spectrum
*graph++ = calibrationCommonGraph(spectrum, FL_NORMAL_XYPLOT, 0, curY, 1200, 100, "DFT output", FL_YELLOW);
fl_get_object_bbox(fdui->graph[1].graph,&x, &y,&w, &h);
curY += h + 20;
// Frequency-domain channel response
*graph++ = calibrationCommonGraph(gNBWaterFall, WATERFALL, 0, curY, 1200, 100, "received signal in time", FL_RED);
fl_get_object_bbox(fdui->graph[2].graph, &x, &y, &w, &h);
curY+=h+20;
// LLR of PUSCH
//fdui->graph[3] = calibrationCommonGraph( puschLLR, FL_POINTS_XYPLOT, 0, curY, 500, 200, "PUSCH Log-Likelihood Ratios (LLR, mag)", FL_YELLOW );
// I/Q PUSCH comp
fdui->graph[2] = calibrationCommonGraph( puschIQ, FL_POINTS_XYPLOT, 500, curY, 300, 200,
"PUSCH I/Q of MF Output", FL_YELLOW );
fl_get_object_bbox(fdui->graph[2].graph,&x, &y,&w, &h);
curY+=h;
//fl_get_object_bbox(fdui->graph[6].graph,&x, &y,&w, &h);
curY+=h;
fdui->graph[3].graph=NULL;
*graph++ = calibrationCommonGraph(signalIQ, FL_POINTS_XYPLOT, 0, curY, 300, 300, "I/Q of frequency domain", FL_YELLOW);
*graph++ = calibrationCommonGraph(signalIQtx, FL_POINTS_XYPLOT, 500, curY, 300, 300, "Tx generated I/Q of frequency domain", FL_YELLOW);
fl_end_form( );
fdui->phy_scope->fdui = fdui;
fl_show_form (fdui->phy_scope, FL_PLACE_HOTSPOT, FL_FULLBORDER, "LTE UL SCOPE gNB");
fl_show_form(fdui->phy_scope, FL_PLACE_HOTSPOT, FL_FULLBORDER, "calibration SCOPE");
return fdui;
}
void calibrationScope(OAI_phy_scope_t *form) {
int i=0;
int len = form->context->dft_sz;
if (!form->freqDomain)
form->freqDomain = malloc16(len * sizeof(*form->freqDomain));
if (!form->timeDomain)
form->timeDomain = malloc16(len * sizeof(*form->timeDomain));
if (!form->freqDomainTx)
form->freqDomainTx = malloc16(len * sizeof(*form->freqDomain));
if (!form->timeDomainTx)
form->timeDomainTx = malloc16(len * sizeof(*form->timeDomainTx));
pthread_mutex_lock(&form->context->rxMutex);
memcpy(form->timeDomain, form->context->samplesRx[0], len * sizeof(*form->timeDomain));
memcpy(form->timeDomainTx, form->context->samplesTx[0], len * sizeof(*form->timeDomainTx));
pthread_mutex_unlock(&form->context->rxMutex);
__attribute__((aligned(32))) c16_t signal[len];
for (int i = 0; i < len; i++)
signal[i] = (c16_t){form->timeDomain[i].r, form->timeDomain[i].i};
dft(get_dft(len), (int16_t *)signal, (int16_t *)form->freqDomain, 1);
for (int i = 0; i < len; i++)
signal[i] = (c16_t){form->timeDomainTx[i].r, form->timeDomainTx[i].i};
dft(get_dft(len), (int16_t *)signal, (int16_t *)form->freqDomainTx, 1);
int i = 0;
while (form->graph[i].graph) {
form->graph[i].funct(form->graph+i, form->context);
form->graph[i].funct(form->graph + i, form);
i++;
}
//fl_check_forms();
}
static void *scopeThread(void *arg) {
calibData_t * context = (calibData_t *)arg;
threads_t *context = (threads_t *)arg;
size_t stksize=0;
pthread_attr_t atr;
pthread_attr_init(&atr);
pthread_attr_getstacksize(&atr, &stksize);
pthread_attr_setstacksize(&atr,32*1024*1024 );
sleep(3); // no clean interthread barriers
pthread_attr_setstacksize(&atr, 32 * 1024 * 1024);
int fl_argc=1;
char *name="Calibration-scope";
fl_initialize (&fl_argc, &name, NULL, 0, 0);
@@ -384,17 +431,17 @@ static void *scopeThread(void *arg) {
while (!oai_exit) {
calibrationScope(form);
usleep(99*1000);
usleep(50 * 1000);
}
return NULL;
}
void CalibrationInitScope(void **samplesRx, openair0_device_t *rfdevice)
void CalibrationInitScope(threads_t *p)
{
pthread_t forms_thread;
calibData_t *tmp = (calibData_t *)malloc_or_fail(sizeof(*tmp));
tmp->samplesRx=samplesRx;
tmp->rfdevice=rfdevice;
threadCreate(&forms_thread, scopeThread, (void*) tmp, "scope", -1, OAI_PRIORITY_RT_LOW);
// calibData_t *tmp = malloc_or_fail(sizeof(*tmp));
// tmp->samplesRx=samplesRx;
// tmp->rfdevice=rfdevice;
threadCreate(&forms_thread, scopeThread, (void *)p, "scope", 7, OAI_PRIORITY_RT_LOW);
}

View File

@@ -1,5 +1,30 @@
#ifndef CALIB_SCOPE_H
#define CALIB_SCOPE_H
void CalibrationInitScope(void **samplesRx, openair0_device_t *rfdevice);
enum e_pattern_scheme { e_SINUS, e_CHIRP, e_QPSK, e_QAM_16, e_QAM_64, e_QAM_256, e_RAMP, e_SIGNATURE, e_MAX_PATT_SCHEME };
typedef struct {
uint tx;
uint rx;
uint freq;
enum e_pattern_scheme tx_pattern;
uint amplitude;
uint sinus_freq;
uint dft;
char * file;
char * dump_iq;
} config_t;
typedef struct {
config_t *c;
openair0_device_t *rfdevice;
int antennas;
int dft_sz;
c16_t **samplesRx;
c16_t **samplesTx;
pthread_mutex_t rxMutex;
pthread_mutex_t txMutex;
} threads_t;
void CalibrationInitScope(threads_t *p);
#endif

View File

@@ -2,14 +2,18 @@
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#define __USE_GNU
#include <stdint.h>
#include "openair1/PHY/defs_common.h"
#include <sys/stat.h>
#include <openair1/PHY/impl_defs_top.h>
#include <radio/COMMON/common_lib.h>
#include <executables/softmodem-common.h>
#include <openair1/PHY/TOOLS/calibration_scope.h>
#include "nfapi/oai_integration/vendor_ext.h"
#include "common/config/config_userapi.h"
#include <arpa/inet.h>
#include <pthread.h>
int oai_exit=false;
unsigned int mmapped_dma=0;
@@ -24,6 +28,7 @@ uint32_t target_dl_mcs = 9;
uint64_t dlsch_slot_bitmap = (1<<1);
uint64_t ulsch_slot_bitmap = (1<<8);
uint32_t target_ul_bw = 50;
uint32_t target_dl_bw = 50;
uint32_t target_dl_Nl;
uint32_t target_ul_Nl;
@@ -35,8 +40,479 @@ uint32_t ulsch_slot_modval;
int read_recplayconfig(recplay_conf_t **recplay_conf, recplay_state_t **recplay_state) {return 0;}
void nfapi_setmode(nfapi_mode_t nfapi_mode) {}
void set_taus_seed(unsigned int seed_init){};
// configmodule_interface_t *uniqCfg = NULL;
openair0_timestamp_t rx_timestamp = 0;
openair0_timestamp_t tx_timestamp = 0;
openair0_timestamp_t last_hole = 0;
const int hole_size=10;
pthread_cond_t tx_trig;
static uint32_t rng_state = 2463534242u; // non-zero seed
static int chip = 16;
static inline uint32_t xorshift32(void)
{
uint32_t x = rng_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
rng_state = x;
return x;
}
/* ------------------ Bit pool ------------------ */
static uint32_t bit_pool = 0;
static int bits_left = 0;
/* Ensure at least n bits available */
static inline void refill_bits(int n)
{
if (bits_left < n) {
bit_pool = xorshift32();
bits_left = 32;
}
}
/* ------------------ Uniform generators ------------------ */
/* 2-bit uniform [0..3] */
static inline uint8_t rand_u2(void)
{
refill_bits(2);
uint8_t val = bit_pool & 0x3;
bit_pool >>= 2;
bits_left -= 2;
return val;
}
/* 4-bit uniform [0..15] */
static inline uint8_t rand_u4(void)
{
refill_bits(4);
uint8_t val = bit_pool & 0xF;
bit_pool >>= 4;
bits_left -= 4;
return val;
}
static inline uint8_t rand_u6(void)
{
refill_bits(6);
uint8_t val = bit_pool & 0x3F; // 6 bits
bit_pool >>= 6;
bits_left -= 6;
return val;
}
/* 8-bit uniform [0..255] */
static inline uint8_t rand_u8(void)
{
refill_bits(8);
uint8_t val = bit_pool & 0xFF;
bit_pool >>= 8;
bits_left -= 8;
return val;
}
static c16_t * read_file (char * filename, int *sz) {
int fd=open(filename,O_RDONLY);
if (fd < 0)
abort();
struct stat st;
stat(filename, &st);
char * f=mmap(NULL,st.st_size ,PROT_READ, MAP_PRIVATE, fd, 0);
char* p=f;
int count=0;
char tmp[64];
char* endfile=f+st.st_size;
do {
while ((*p < '0' || *p > '9') && *p != '-' && p<endfile)
p++;
if (p != endfile) {
count ++;
// begining of number
char * end=p;
while (((*end >= '0' && *end <= '9') || *end == '-') && end< endfile)
end++;
p=end;
}
} while (p && p<endfile);
int16_t *vect=malloc(count * sizeof(*vect));
int16_t* ptr=vect;
p=f;
do {
while ((*p < '0' || *p > '9') && *p != '-' && p<endfile)
p++;
if (p != endfile) {
// begining of number
char * end=p;
while (((*end >= '0' && *end <= '9') || *end == '-') && end< endfile)
end++;
memcpy(tmp,p,end-p);
tmp[end-p]=0;
*ptr++=atoi(tmp);
p=end;
}
} while (p && p<endfile);
*sz=count/2;
return (c16_t*)vect;
}
void *write_thread(void *arg)
{
threads_t *params = (threads_t *)arg;
c16_t **samplesTx = params->samplesTx;
uint64_t ts = 0;
const float WAVE_AMP = params->c->amplitude;
const float sin_freq = params->c->sinus_freq;
c16_t * file_input=NULL;
int num_samples=0;
if ( params->c->file)
file_input=read_file(params->c->file, &num_samples);
else {
switch (params->c->tx_pattern) {
case e_CHIRP: {
double Fs = 122880.0;
double f0 = -30 * 1000.0; // start freq
double f1 = 30 * 1000.0; // end freq
double T = params->dft_sz / Fs;
double k = (f1 - f0) / T; // Hz/s sweep rate
for (int i = 0; i < params->dft_sz; i++) {
double t = ts / Fs;
double phase = 2 * M_PI * (f0 * t + 0.5 * k * t * t);
samplesTx[0][i].r = WAVE_AMP * cos(phase);
samplesTx[0][i].i = WAVE_AMP * sin(phase);
ts++;
}
} break;
case e_QPSK: {
__attribute__((aligned(32))) c16_t freq_signal[params->dft_sz];
int val = 0;
const float required_BW = 6000.0e3;
const float dft_binsize = ((float)122.88e6 / (float)params->dft_sz);
const float sqrt2 = 0.70711;
int amp = WAVE_AMP * sqrt2 * sqrt2;
int center = dft_binsize / 2;
int bin_masking = (int)(((float)122.88e6 - required_BW) / (float)dft_binsize);
for (int carrier = 0; carrier < params->dft_sz; carrier++) {
if (carrier >= bin_masking && carrier <= (center + bin_masking)) {
int i = rand_u2(); // rand() % 4;
val ^= 1 << i;
freq_signal[carrier] = (c16_t){(1 - 2 * (val & 1)) * amp, (1 - 2 * ((val >> 1) & 1)) * amp};
} else {
freq_signal[carrier].r = 0;
freq_signal[carrier].i = 0;
}
ts++;
}
dft(get_dft(params->dft_sz), (int16_t *)freq_signal, (int16_t *)samplesTx[0], 1);
} break;
case e_QAM_16: {
__attribute__((aligned(32))) c16_t freq_signal[params->dft_sz];
const float required_BW = 15000.0e3;
const float dft_binsize = ((float)122.88e6 / (float)params->dft_sz);
const float sqrt2 = 0.70711;
const float sqrt10 = 0.31623;
int center = dft_binsize / 2;
int amp = WAVE_AMP * sqrt10 * sqrt2;
int bin_masking = (int)(((float)122.88e6 - required_BW) / (float)dft_binsize);
for (int carrier = 0; carrier < params->dft_sz; carrier++) {
if (carrier >= bin_masking && carrier <= (center + bin_masking)) {
int i = rand_u4(); // rand() % 16;
freq_signal[carrier].r = (1 - 2 * (i & 1)) * (2 - (1 - 2 * ((i >> 2) & 1))) * amp;
freq_signal[carrier].i = (1 - 2 * ((i >> 1) & 1)) * (2 - (1 - 2 * ((i >> 3) & 1))) * amp;
} else {
freq_signal[carrier].r = 0;
freq_signal[carrier].i = 0;
}
ts++;
}
dft(get_dft(params->dft_sz), (int16_t *)freq_signal, (int16_t *)samplesTx[0], 1);
} break;
case e_QAM_64: {
__attribute__((aligned(32))) c16_t freq_signal[params->dft_sz];
const float required_BW = 12000.0e3;
const float dft_binsize = ((float)122.88e6 / (float)params->dft_sz);
const float sqrt2 = 0.70711;
const float sqrt42 = 0.154303;
int center = dft_binsize / 2;
int amp = WAVE_AMP * sqrt42 * sqrt2;
int bin_masking = (int)(((float)122.88e6 - required_BW) / (float)dft_binsize);
for (int carrier = 0; carrier < params->dft_sz; carrier++) {
if (carrier >= bin_masking && carrier <= (center + bin_masking)) {
int i = rand_u6(); // rand() % 64;
freq_signal[carrier] =
(c16_t){((1 - 2 * (i & 1)) * (4 - (1 - 2 * ((i >> 2) & 1)) * (2 - (1 - 2 * ((i >> 4) & 1))))) * amp,
((1 - 2 * ((i >> 1) & 1)) * (4 - (1 - 2 * ((i >> 3) & 1)) * (2 - (1 - 2 * ((i >> 5) & 1))))) * amp};
} else {
freq_signal[carrier].r = 0;
freq_signal[carrier].i = 0;
}
ts++;
}
dft(get_dft(params->dft_sz), (int16_t *)freq_signal, (int16_t *)samplesTx[0], 1);
} break;
case e_QAM_256: {
__attribute__((aligned(32))) c16_t freq_signal[params->dft_sz] = {};
const float required_BW = 6000.0e3;
const float dft_binsize = ((float)122.88e6 / (float)params->dft_sz);
int center = dft_binsize / 2;
float sqrt42 = 0.15430;
const float sqrt2 = 0.70711;
int amp = WAVE_AMP * sqrt42 * sqrt2;
int bin_masking = (int)(((float)122.88e6 - required_BW) / (float)dft_binsize);
for (int carrier = 0; carrier < params->dft_sz; carrier++) {
if (carrier >= bin_masking && carrier <= (center + bin_masking)) {
int i = rand_u8(); // rand() % 256;
freq_signal[carrier] = (c16_t){
((1 - 2 * (i & 1)) * (8 - (1 - 2 * ((i >> 2) & 1)) * (4 - (1 - 2 * ((i >> 4) & 1)) * (2 - (1 - 2 * ((i >> 6) & 1))))))
* amp,
(1 - 2 * ((i >> 1) & 1))
* (8 - (1 - 2 * ((i >> 3) & 1)) * (4 - (1 - 2 * ((i >> 5) & 1)) * (2 - (1 - 2 * ((i >> 7) & 1))))) * amp};
} else {
freq_signal[carrier].r = 0;
freq_signal[carrier].i = 0;
}
ts++;
}
dft(get_dft(params->dft_sz), (int16_t *)freq_signal, (int16_t *)samplesTx[0], 1);
} break;
case e_SINUS:
for (int i = 0; i < params->dft_sz; i++) {
// Better to select a frequency having an integer division with the sampling rate to avoid having DFT leakage later on
// .r = cos and .i = sin -> having a positive spectrum
// For negative spectrum -> .r = sin and .i = cos
samplesTx[0][i].r = WAVE_AMP * cos((ts * M_PI * 2 * sin_freq) / 122880000);
samplesTx[0][i].i = WAVE_AMP * sin((ts * M_PI * 2 * sin_freq) / 122880000); // samplesTx[0][i].r;
// Hamming Window - to allow some pseudo-continuity between batches as this is not a continuously generated signal as in
// real life samplesTx[0][i].r = (samplesTx[0][i].r) * (0.54 - 0.46 * cos(2 * M_PI * i / (params->dft_sz-1)));
// samplesTx[0][i].i = (samplesTx[0][i].i) * (0.54 - 0.46 * cos(2 * M_PI * i / (params->dft_sz-1)));
// samplesTx[0][i].r = (samplesTx[0][i].r) * (0.54 - 0.46 * cos(2 * M_PI * i / (params->dft_sz-1)));
ts++;
}
break;
case e_RAMP:
num_samples = 2048;
const int16_t RAMP_STEP_SIZE = 1;
file_input=malloc(num_samples * sizeof(*file_input));
for (int i = 0; i < num_samples; i++) {
file_input[i] = (c16_t){i * RAMP_STEP_SIZE, 2047 - i * RAMP_STEP_SIZE};
// printf("%d, %d\n", file_input[i].r, file_input[i].i);
}
break;
case e_SIGNATURE:
break;
default:
abort();
}
if (params->c->dump_iq) {
FILE* h=fopen(params->c->dump_iq,"w");
for (int i=0; i<params->dft_sz; i++)
fprintf(h, "%04hX%04hX\n", samplesTx[0][i].r,samplesTx[0][i].i);
fclose(h);
}
}
double avg = 0;
for (int i = 0; i < params->dft_sz; i++) {
avg += sqrt(squaredMod(samplesTx[0][i]));
}
printf("avg: %f \n", avg / params->dft_sz);
uint64_t count = 0;
struct timespec last_second;
clock_gettime(CLOCK_REALTIME, &last_second);
openair0_timestamp_t last_tx_timestamp = 0;
// this is tx ahead in main application, the driver has it's tx ahead that should be smaller to prevent starvation
const int tx_ahead = params->dft_sz * 20;
char *hole_flag = getenv("HOLE");
uint64_t num_samples_file=0;
uint64_t tx_cnt = 0;
while (!oai_exit) {
openair0_timestamp_t new_tx;
if (getenv("FAKE_RX") && tx_cnt > atoi(getenv("FAKE_RX"))) {
new_tx = last_tx_timestamp + params->dft_sz;
} else {
do {
AssertFatal(!pthread_mutex_lock(&params->txMutex), "");
AssertFatal(!pthread_cond_wait(&tx_trig, &params->txMutex), "");
new_tx = tx_timestamp;
AssertFatal(!pthread_mutex_unlock(&params->txMutex), "");
} while (last_tx_timestamp == new_tx);
}
tx_cnt++;
if (last_tx_timestamp + params->dft_sz != new_tx)
LOG_D(HW, "not continuous %ld\n", new_tx - (last_tx_timestamp + params->dft_sz));
if (abs(last_tx_timestamp - new_tx) > 1228800) {
LOG_W(HW, "large tx gap %ld\n", new_tx - (last_tx_timestamp + params->dft_sz));
last_tx_timestamp = new_tx - params->dft_sz;
}
do {
last_tx_timestamp += params->dft_sz;
if (params->c->tx_pattern == e_SIGNATURE) {
uint64_t signature=last_tx_timestamp+ tx_ahead;
int nb_bits=sizeof(signature)*8;
memset(samplesTx[0],0,params->dft_sz*sizeof(c16_t));
for (int i=0; i<params->dft_sz/chip; i++) {
c16_t val={};
if (signature & (1ULL << (i%nb_bits))) {
if (i&1)
val=(c16_t){WAVE_AMP,WAVE_AMP};
else
val=(c16_t){-WAVE_AMP,-WAVE_AMP};
}
for (int j=0; j<chip; j++)
samplesTx[0][i*chip+j]=val;
}
}
if (num_samples) {
for (int i=0; i< params->dft_sz; i++)
samplesTx[0][i]=file_input[(num_samples_file++)%num_samples];
}
c16_t tmp[hole_size];
int loc=-1;
if (hole_flag && count % 1935 == 0) {
loc=((uint)rand())%(params->dft_sz-hole_size);
memcpy(tmp, samplesTx[0]+loc,sizeof(tmp));
memset(samplesTx[0]+loc, 0,sizeof(tmp));
AssertFatal(!pthread_mutex_lock(&params->txMutex), "");
last_hole=last_tx_timestamp + loc + tx_ahead;
LOG_W(HW,"Set hole for: %lu\n", last_hole);
AssertFatal(!pthread_mutex_unlock(&params->txMutex), "");
}
params->rfdevice
->trx_write_func(params->rfdevice, last_tx_timestamp + tx_ahead, (void **)samplesTx, params->dft_sz, params->antennas, 0);
if(loc >= 0)
memcpy(samplesTx[0]+loc, tmp, sizeof(tmp));
count++;
} while (last_tx_timestamp < new_tx);
last_tx_timestamp = new_tx;
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
if (now.tv_sec != last_second.tv_sec) {
LOG_D(HW, "write thread wrote %lu times in one second\n", count);
last_second = now;
count = 0;
}
}
return NULL;
}
void *read_thread(void *arg)
{
threads_t *params = (threads_t *)arg;
c16_t **samplesRx = params->samplesRx;
uint64_t count = 0;
int warmup=0;
struct timespec last_second;
clock_gettime(CLOCK_REALTIME, &last_second);
uint64_t old_sign=0;
int nb_aligned = 0;
while (!oai_exit) {
uint64_t old = rx_timestamp;
__attribute__((aligned(32))) c16_t rx[ params->dft_sz ];
c16_t *rxptr=rx;
int ret =
params->rfdevice->trx_read_func(params->rfdevice, &rx_timestamp, (void **)&rxptr, params->dft_sz, params->antennas);
if (old + params->dft_sz != rx_timestamp)
LOG_E(HW, "not continuous rx %ld\n", rx_timestamp - (old + params->dft_sz));
if (ret != params->dft_sz)
printf("read of :%d\n", ret);
count++;
AssertFatal(!pthread_mutex_lock(&params->rxMutex), "");
memcpy(samplesRx[0],rx, sizeof(rx));
// LOG_E(HW,"signal: %lu\n", tx_timestamp);
/*
for (int i = 0; i < params->dft_sz; i++)
params->samplesRx[0][i] = (c16_t){params->samplesRx[0][i].r >>2, params->samplesRx[0][i].i >>2};
*/
double min=UINT64_MAX, tot_pow=0, tot_samples=0;
int min_pos=0;
if (getenv("HOLE")) {
for (int i = 0; i < params->dft_sz-hole_size; i++) {
double local=0;
for (int j=i; j<i+hole_size; j++) {
local+=params->samplesRx[0][j].r*params->samplesRx[0][j].r+params->samplesRx[0][j].i*params->samplesRx[0][j].i;
}
tot_samples+=hole_size;
tot_pow+=local;
if (local < min ) {
min=local;
min_pos=i;
}
}
}
if (params->c->tx_pattern == e_SIGNATURE) {
int sz=sizeof(rx_timestamp)*8;
float sign[sz]={};
for (int i=0; i<params->dft_sz/chip; i++){
int bit=i%sz;
c16_t *tmp=rx+i*chip;
for (int j=0; j<chip; j++)
sign[bit]+=tmp[j].r*tmp[j].r+tmp[j].i*tmp[j].i;
}
float max=0;
for (int i = 0; i < sz; i++)
if (sign[i] >max)
max=sign[i];
uint64_t encoded_ts=0;
for (int i = 0; i < sz; i++)
if (sign[i] > max / 2)
encoded_ts |= 1ULL << i;
if (rx_timestamp == encoded_ts)
nb_aligned++;
else {
printf("after %d aligned packets, %lx, %lx diff with previous signature %ld, diff with header : %ld\n",
nb_aligned,
rx_timestamp,
encoded_ts,
(int64_t)encoded_ts - old_sign,
rx_timestamp - encoded_ts);
nb_aligned = 0;
}
old_sign=encoded_ts;
}
AssertFatal(!pthread_mutex_lock(&params->txMutex), "");
tx_timestamp = rx_timestamp;
if (min < tot_pow/(2*tot_samples)) {
LOG_I(HW, "found hole %lu, programmed for %lu, received %ld later\n", min_pos+rx_timestamp, last_hole,min_pos+rx_timestamp - last_hole );
}
warmup++;
if (warmup > 1024)
AssertFatal(!pthread_cond_signal(&tx_trig), "");
AssertFatal(!pthread_mutex_unlock(&params->txMutex), "");
AssertFatal(!pthread_mutex_unlock(&params->rxMutex), "");
// dft(get_dft(len), (int16_t *)form->timeDomain, (int16_t *)form->freqDomain, 1);
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
if (now.tv_sec != last_second.tv_sec) {
printf("read thread got %lu blocks in one second, samples per block: %d, nb samples: %lu\n", count, ret, count*ret);
count=0;
last_second.tv_sec++;
#if 0
FILE *fd = fopen("trace.iq", "w+");
if (!fd)
abort();
/* We should advance +1 only if header was detected in previous steps
* Which will make the read working even without timestamped rxdata
*/
c16_t *s = samplesRx[0]; /* Exclude the header from the samples */
for (int i = 0; i < ret; i++) {
/* We need to throw the entie 256-bits word if we detect the 64-bits header.
* This may happens when receiving a big packet size in chuncks.
*/
fprintf(fd, "%d %d %d\n", i, s[i].r, s[i].i);
}
#endif
}
}
return NULL;
}
int main(int argc, char **argv) {
///static configuration for NR at the moment
/// static configuration for NR at the moment
if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) {
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
}
@@ -44,245 +520,123 @@ int main(int argc, char **argv) {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
logInit();
paramdef_t cmdline_params[] = CMDLINE_PARAMS_DESC_GNB ;
CONFIG_SETRTFLAG(CONFIG_NOEXITONHELP);
get_common_options(uniqCfg);
config_t c = {1, 1, 3750000, 1, 2047, 10000, 8192, NULL};
paramdef_t cmdline_params[] = {
{"tx", "enable tx", 0, .uptr = &c.tx, .defintval = 1, TYPE_UINT, 0},
{"rx", "enable tx", 0, .uptr = &c.rx, .defintval = 1, TYPE_UINT, 0},
{"freq", "center frequency in kHz", 0, .uptr = &c.freq, .defintval = 1, TYPE_UINT, 0},
{"tx_pattern",
"generate signal for a sine (0), chirp (1), qpsk (2), qam-16 (3), qam-64 (4), qam-256 (5)",
0,
.uptr = &c.tx_pattern,
.defintval = 2,
TYPE_UINT,
0},
{"amplitude", "signal amplitude (int16)", 0, .uptr = &c.amplitude, .defintval = 2047, TYPE_UINT, 0},
{"sinus_freq", "if chirp is false, sinut frequency in KHz", .uptr = &c.sinus_freq, .defintval = 10000, TYPE_UINT, 0},
{"dft", "dft size for signal frequency/time convertion", .uptr = &c.dft, .defintval = 8192, TYPE_UINT, 0},
{"file", "input I/Q samples in ascii, sequence I then Q\n", PARAMFLAG_MALLOCINCONFIG, .strptr = &c.file, .defstrval = NULL, TYPE_STRING, 0},
{"dump_iq", "dump the tx iq file at begining", PARAMFLAG_MALLOCINCONFIG, .strptr = &c.dump_iq, .defstrval = NULL, TYPE_STRING, 0},
};
config_process_cmdline(uniqCfg, cmdline_params, sizeofArray(cmdline_params), NULL);
CONFIG_CLEARRTFLAG(CONFIG_NOEXITONHELP);
lock_memory_to_ram();
int sampling_rate=30.72e6;
int DFT=2048;
int TxAdvanceInDFTSize=12;
int antennas=1;
uint64_t freq=3619.200e6;
int rxGain=90;
int txGain=90;
int filterBand=40e6;
char * usrp_addrs="type=b200";
int h=open("/dev/cpu_dma_latency", 0666);
int lat=2; // micro second
assert(sizeof(lat)==write(h,&lat,sizeof(lat)));
openair0_config_t openair0_cfg= {
//! the sample rate for both transmit and receive.
.sample_rate=sampling_rate,
//! samples per packet on the fronthaul interface
.samples_per_packet=1024,
//! number of RX channels (=RX antennas)
.rx_num_channels=antennas,
//! number of TX channels (=TX antennas)
.tx_num_channels=antennas,
//! \brief Center frequency in Hz for RX.
//! index: [0..rx_num_channels[
.rx_freq={freq,freq,freq,freq},
//! \brief Center frequency in Hz for TX.
//! index: [0..rx_num_channels[ !!! see lte-ue.c:427 FIXME iterates over rx_num_channels
.tx_freq={freq,freq,freq,freq},
//! \brief Gain for RX in dB.
//! index: [0..rx_num_channels]
.rx_gain={rxGain,rxGain,rxGain,rxGain},
//! gain for TX in dB
.tx_gain={txGain,txGain,txGain,txGain},
//! RX bandwidth in Hz
.rx_bw=filterBand,
//! TX bandwidth in Hz
.tx_bw=filterBand,
//! clock source
.clock_source=external,//internal gpsdo external
//! timing_source
.time_source=internal, //internal gpsdo external
//! Manual SDR IP address
.sdr_addrs=usrp_addrs,
int sampling_rate = 30.72e6 * 6; // for X300 test, OC will overload it
int antennas = 1;
uint64_t freq = c.freq * 1000;
int rxGain = 90;
int txGain = 0;
int filterBand = 40e6;
openair0_config_t openair0_cfg = {
.duplex_mode = duplex_mode_TDD,
.sample_rate = sampling_rate,
.num_rb_dl=-1, // flag to say we are rftest, don't scale IQ samples for OAI
.tx_sample_advance = 0,
.rx_num_channels = antennas,
.tx_num_channels = antennas,
.rx_freq = {freq, freq, freq, freq},
.tx_freq = {freq, freq, freq, freq},
.rx_gain_calib_table = NULL,
.rx_gain = {rxGain, rxGain, rxGain, rxGain},
.tx_gain = {txGain, txGain, txGain, txGain},
.rx_bw = filterBand,
.tx_bw = filterBand,
.clock_source = external, // internal gpsdo external
.time_source = internal, // internal gpsdo external
.sdr_addrs = "addr=192.168.30.2",
.autocal = {0},
//! rf devices work with x bits iqs when oai have its own iq format
//! the two following parameters are used to convert iqs
.configFilename = "",
.recplay_mode = 0,
.recplay_conf = NULL,
};
//-----------------------
openair0_device_t rfdevice= {
/*!brief Type of this device */
.type=NONE_DEV,
/*!brief Transport protocol type that the device supports (in case I/Q samples need to be transported) */
.transp_type=NONE_TP,
/*!brief Type of the device's host (RAU/RRU) */
.host_type=MIN_HOST_TYPE,
/* !brief RF frontend parameters set by application */
.openair0_cfg=NULL, //set by device_init
/* !brief ETH params set by application */
.eth_params=NULL,
//! record player data, definition in record_player.h
.recplay_state=NULL,
/* !brief Indicates if device already initialized */
.is_init=0,
/*!brief Can be used by driver to hold internal structure*/
.priv=NULL,
/* Functions API, which are called by the application*/
/*! \brief Called to start the transceiver. Return 0 if OK, < 0 if error
@param device pointer to the device structure specific to the RF hardware target
*/
.trx_start_func=NULL,
/*! \brief Called to configure the device
@param device pointer to the device structure specific to the RF hardware target
*/
.trx_config_func=NULL,
/*! \brief Called to send a request message between RAU-RRU on control port
@param device pointer to the device structure specific to the RF hardware target
@param msg pointer to the message structure passed between RAU-RRU
@param msg_len length of the message
*/
.trx_ctlsend_func=NULL,
/*! \brief Called to receive a reply message between RAU-RRU on control port
@param device pointer to the device structure specific to the RF hardware target
@param msg pointer to the message structure passed between RAU-RRU
@param msg_len length of the message
*/
.trx_ctlrecv_func=NULL,
/*! \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 number of antennas
@param flags flags must be set to TRUE if timestamp parameter needs to be applied
*/
.trx_write_func=NULL,
/*! \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 (1 dimensional)
@param nsamps number of samples to be sent
@param antenna_id index of the antenna if the device has multiple anteannas
@param flags flags must be set to TRUE if timestamp parameter needs to be applied
*/
.trx_write_func2=NULL,
/*! \brief Receive samples from hardware.
* Read \ref 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 \ref 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
* \returns the number of sample read
*/
.trx_read_func=NULL,
/*! \brief Receive samples from hardware, this version provides a single antenna at a time and returns.
* Read \ref 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 A pointers to a buffer for received samples. The buffer must be large enough to hold the number of samples \ref nsamps.
* \param nsamps Number of samples. One sample is 2 byte I + 2 byte Q => 4 byte.
* \param antenna_id Index of antenna from which samples were received
* \returns the number of sample read
*/
.trx_read_func2=NULL,
/*! \brief print the device statistics
* \param device the hardware to use
* \returns 0 on success
*/
/*! \brief print the device statistics
* \param device the hardware to use
* \returns 0 on success
*/
.trx_get_stats_func=NULL,
/*! \brief Reset device statistics
* \param device the hardware to use
* \returns 0 in success
*/
.trx_reset_stats_func=NULL,
/*! \brief Terminate operation of the transceiver -- free all associated resources
* \param device the hardware to use
*/
.trx_end_func=NULL,
/*! \brief Stop operation of the transceiver
*/
.trx_stop_func=NULL,
/* Functions API related to UE*/
/*! \brief Set RX feaquencies
* \param device the hardware to use
* \param openair0_cfg RF frontend parameters set by application
* \returns 0 in success
*/
.trx_set_freq_func=NULL,
/*! \brief Set gains
* \param device the hardware to use
* \param openair0_cfg RF frontend parameters set by application
* \returns 0 in success
*/
.trx_set_gains_func=NULL,
/*! \brief RRU Configuration callback
* \param idx RU index
* \param arg pointer to capabilities or configuration
*/
.configure_rru=NULL,
/*! \brief Pointer to generic RRU private information
*/
.thirdparty_priv=NULL,
.thirdparty_init=NULL,
/*! \brief Callback for Third-party RRU Cleanup routine
\param device the hardware configuration to use
*/
.thirdparty_cleanup=NULL,
/*! \brief Callback for Third-party start streaming routine
\param device the hardware configuration to use
*/
.thirdparty_startstreaming=NULL,
/*! \brief RRU Configuration callback
* \param idx RU index
* \param arg pointer to capabilities or configuration
*/
.trx_write_init=NULL,
/* \brief Get internal parameter
* \param id parameter to get
* \return a pointer to the parameter
*/
.get_internal_parameter=NULL,
openair0_device_t rfdevice = {
/*!brief Type of this device */
.type = NONE_DEV,
/*!brief Transport protocol type that the device supports (in case I/Q samples need to be transported) */
.transp_type = NONE_TP,
/*!brief Type of the device's host (RAU/RRU) */
.host_type = MIN_HOST_TYPE,
/* !brief RF frontend parameters set by application */
.openair0_cfg = NULL, // set by device_init
/* !brief ETH params set by application */
.eth_params = NULL,
//! record player data, definition in record_player.h
.recplay_state = NULL,
/* !brief Indicates if device already initialized */
.is_init = 0,
/*!brief Can be used by driver to hold internal structure*/
.priv = NULL,
};
openair0_device_load(&rfdevice,&openair0_cfg);
void ** samplesRx = (void **)malloc16(antennas* sizeof(c16_t *) );
void ** samplesTx = (void **)malloc16(antennas* sizeof(c16_t *) );
openair0_device_load(&rfdevice, &openair0_cfg);
int fd=open(getenv("rftestInputFile"),O_RDONLY);
AssertFatal(fd>=0,"%s",strerror(errno));
for (int i=0; i<antennas; i++) {
samplesRx[i] = (int32_t *)malloc16_clear( DFT*sizeof(c16_t) );
samplesTx[i] = (int32_t *)malloc16_clear( DFT*sizeof(c16_t) );
printf("generate a sinus wave at middle RB");
load_dftslib();
c16_t **samplesRx = malloc16(antennas * sizeof(c16_t *));
for (int i = 0; i < antennas; i++) {
samplesRx[i] = malloc16_clear(c.dft * sizeof(c16_t));
}
c16_t **samplesTx = malloc16(antennas * sizeof(c16_t *));
for (int i = 0; i < antennas; i++) {
samplesTx[i] = malloc16_clear(c.dft * sizeof(c16_t));
}
CalibrationInitScope(samplesRx, &rfdevice);
openair0_timestamp_t timestamp=0;
/* scopedata shall be filled from a software FIFO and not directly from the samples */
threads_t params = (threads_t){&c, &rfdevice, antennas, c.dft, samplesRx, samplesTx};
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
AssertFatal(!pthread_mutex_init(&params.txMutex, &attr), "");
AssertFatal(!pthread_mutex_init(&params.txMutex, &attr), "");
AssertFatal(!pthread_cond_init(&tx_trig, NULL), "");
CalibrationInitScope(&params);
rfdevice.trx_start_func(&rfdevice);
while(!oai_exit) {
for (int i=0; i<antennas; i++) {
ssize_t len = read(fd, samplesTx[i], DFT*sizeof(c16_t));
if (len < 0) {
fprintf(stderr, "error during read(): errno %d, %s\n", errno, strerror(errno));
exit(1);
}
}
rfdevice.trx_read_func(&rfdevice, &timestamp, samplesRx, DFT, antennas);
rfdevice.trx_write_func(&rfdevice, timestamp + TxAdvanceInDFTSize * DFT, samplesTx, DFT, antennas, 0);
}
pthread_t w_thread;
if (c.tx)
threadCreate(&w_thread, write_thread, &params, "write_thr", 3, OAI_PRIORITY_RT);
pthread_t r_thread;
if (c.rx)
threadCreate(&r_thread, read_thread, &params, "read_thr", 2, OAI_PRIORITY_RT);
if (c.tx)
(void)pthread_join(w_thread, NULL);
if (c.rx)
(void)pthread_join(r_thread, NULL);
return 0;
}

View File

@@ -284,7 +284,8 @@ static void oai_xygraph_getbuff(OAIgraph_t *graph, float **x, float **y, int len
}
#endif
static void oai_xygraph(OAIgraph_t *graph, float *x, float *y, int len, int layer, bool NoAutoScale) {
static void oai_xygraph(OAIgraph_t *graph, float *x, float *y, int len, int layer, int NoAutoScale)
{
#ifdef WEBSRVSCOPE
websrv_scopedata_msg_t *msg = NULL;
@@ -834,7 +835,7 @@ static void uePbchLLR (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_
llr_pbch[i] = llrs[i];
}
#endif
oai_xygraph(graph, bit_pbch, llr_pbch, nx, 0, 10);
oai_xygraph(graph, bit_pbch, llr_pbch, nx, 0, 100);
}
static void uePbchIQ (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) {
@@ -859,7 +860,7 @@ static void uePbchIQ (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_U
Q[i]=pbch_comp[i].i;
}
#endif
oai_xygraph(graph, I, Q, newsz, 0, true);
oai_xygraph(graph, I, Q, newsz, 0, 100);
}
static void uePcchLLR (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) {
@@ -883,7 +884,7 @@ static void uePcchLLR (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_
llr[i] = (float) pdcch_llr[i];
}
#endif
oai_xygraph(graph, bit, llr, nx, 0, 10);
oai_xygraph(graph, bit, llr, nx, 0, 100);
}
static void uePcchIQ (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) {
// PDCCH I/Q of MF Output
@@ -902,8 +903,11 @@ static void uePcchIQ (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_U
oai_xygraph_getbuff(graph, &I, &Q, sz, 0);
for (int i=0; i<sz; i++) {
I[i] = pdcch_comp[i].r;
Q[i] = pdcch_comp[i].i;
if (pdcch_comp[i].r < 100 && pdcch_comp[i].i < 100) {
I[i] = pdcch_comp[i].r;
Q[i] = pdcch_comp[i].i;
} else
I[i] = Q[i] = 0;
}
#endif
oai_xygraph(graph, I, Q, newsz, 0, 10);

View File

@@ -1883,9 +1883,15 @@ static void nr_rrc_ue_decode_NR_BCCH_BCH_Message(NR_UE_RRC_INST_t *rrc,
LOG_E(NR_RRC, "NR_BCCH_BCH decode error\n");
return;
}
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_BCCH_BCH_Message, (void *)bcch_message);
if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
static time_t t = 0;
time_t t2 = time(0);
if (t != t2) {
xer_fprint(stdout, &asn_DEF_NR_BCCH_BCH_Message, (void *)bcch_message);
t = t2;
}
}
// Actions following cell selection while T311 is running
NR_UE_Timers_Constants_t *timers = &rrc->timers_and_constants;
if (nr_timer_is_active(&timers->T311)) {

View File

@@ -47,6 +47,11 @@ if(OAI_VRTSIM)
add_subdirectory(vrtsim)
endif()
add_boolean_option(OAI_OC OFF "Activate OAI's USRP driver" OFF)
if(OAI_OC)
add_subdirectory(oc)
endif()
add_boolean_option(OAI_RF_EMULATOR ON "Activate OAI's RF emulator" OFF)
if(OAI_RF_EMULATOR)
add_subdirectory(emulator)

View File

@@ -24,7 +24,7 @@
#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"};
{"", "USRP B200", "USRP X300", "USRP N300", "USRP X400", "BLADERF", "LMSSDR", "IRIS", "No HW", "UEDv2", "RFSIMULATOR", "OC1"};
const char *get_devname(int devtype) {
if (devtype < MAX_RF_DEV_TYPE && devtype !=MIN_RF_DEV_TYPE )

View File

@@ -76,6 +76,7 @@ typedef enum {
/*!\brief device is UEDv2 */
UEDv2_DEV,
RFSIMULATOR,
OC1_DEV,
MAX_RF_DEV_TYPE
} dev_type_t;
/* list of names of devices, needs to match dev_type_t */

View File

@@ -2,10 +2,10 @@
add_compile_options(-Wunused-parameter)
#find_package(Boost REQUIRED)
find_package(UHD REQUIRED UHD)
#find_package(UHD REQUIRED UHD)
add_library(oai_usrpdevif MODULE usrp_lib.cpp)
#target_include_directories(oai_usrpdevif PRIVATE Boost::boost)
target_include_directories(oai_usrpdevif PRIVATE Boost::boost)
target_link_libraries(oai_usrpdevif PRIVATE ${UHD_LIBRARIES})
target_include_directories(oai_usrpdevif PRIVATE ${UHD_INCLUDE_DIRS})
target_link_libraries(oai_usrpdevif PRIVATE log_headers)

View File

@@ -1190,7 +1190,7 @@ extern "C" {
case 184320000:
// from usrp_time_offset
// openair0_cfg[0].samples_per_packet = 2048;
openair0_cfg[0].tx_sample_advance = 15; // to be checked
openair0_cfg[0].tx_sample_advance = 132; // to be checked
openair0_cfg[0].tx_bw = 100e6;
openair0_cfg[0].rx_bw = 100e6;
break;

7
radio/oc/CMakeLists.txt Normal file
View File

@@ -0,0 +1,7 @@
add_library(oai_ocdevif MODULE oc_lib.cpp)
target_link_libraries(oai_ocdevif PRIVATE UTIL)
set_target_properties(oai_ocdevif PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
add_custom_command(TARGET oai_ocdevif POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink liboai_ocdevif.so liboai_device.so
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

42
radio/oc/README.md Normal file
View File

@@ -0,0 +1,42 @@
[[_TOC_]]
# Open Cells SDR device documentation
## General
OAI works with open cells SDR, compatible with USRP config files
Example files can be found in the `ci-scripts/conf_files/` directory with a
`usrp` in the name, for instance
[`gnb.sa.band78.106prb.usrpn310.ddsuu-2x2.conf`](../../ci-scripts/conf_files/gnb.sa.band78.106prb.usrpn310.ddsuu-2x2.conf).
## Configuration
to use OC SDR driver, add --device.name oai_ocdevif on the command line, or the equivalent value in the configuration file (any OAI executable)
You can specify to use external or interal clock or time source either by
adding the parameters in the `sdr_addrs` field or by using the fields
`clock_src` or `time_src`
Valid choices for clock and time source are `internal`, `external`, and `gpsdo`.
```bash
device = {
name="oai_ocdevif";
}
RUs = (
{
local_rf = "yes"
nb_tx = 2
nb_rx = 2
att_tx = 0
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
eNB_instances = [0];
}
);
```

911
radio/oc/oc_lib.cpp Normal file
View File

@@ -0,0 +1,911 @@
/*
* Licensed by open cells project
*/
#include <iostream>
#include <complex>
#include <fstream>
#include <cmath>
#include <cassert>
#include <queue>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#ifdef OAI_INTEGRATION
#include "common_lib.h"
#include "assertions.h"
#else
// #define LOG_E(m, a...) printf(a)
#include "common_lib.h"
#endif
#include "system.h"
#include <sys/resource.h>
#include "common/platform_types.h"
#include "openair1/PHY/sse_intrin.h"
#include "common/utils/LOG/log.h"
#include "common/utils/time_meas.h"
// Thread-safe queue
template <typename T>
class TSQueue {
private:
// Underlying queue
// mutex for thread synchronization
std::mutex m_mutex;
// Condition variable for signaling
std::condition_variable m_cond;
public:
std::queue<T> m_queue;
// Pushes an element to the queue
void push(T item)
{
// Acquire lock
std::unique_lock<std::mutex> lock(m_mutex);
// Add item
m_queue.push(item);
// Notify one thread that
// is waiting
m_cond.notify_one();
}
// Pops an element off the queue
T pop()
{
// acquire lock
std::unique_lock<std::mutex> lock(m_mutex);
// wait until queue is not empty
m_cond.wait(lock, [this]() { return !m_queue.empty(); });
// retrieve item
T item = m_queue.front();
m_queue.pop();
// return item
return item;
}
};
#define DEVICE_WRITE_DEFAULT "/dev/xdma0_h2c_0"
#define DEVICE_READ_DEFAULT "/dev/xdma0_c2h_0"
static const uint64_t magic_tx = 0xA5A50be3A5A5A5A5LL;
static const uint64_t magic_rx = 0xA5A50be3A5A5A5A5LL;
static const uint32_t magic_footer1 = 0xce11;
static const uint32_t magic_footer2 = 0x5A;
#define READ_BLOCK_NB_SAMPLES 2048
#define NB_BLOCKS_PER_READ 4
#define WRITE_BLOCK_NB_SAMPLES 2048 * 4
#define NB_BLOCKS_PER_WRITE 2
static const uint64_t tx_ahead_max = 32 * 2048;
typedef struct {
uint64_t control;
uint32_t packetSeqNum: 16;
uint32_t packetSz: 16;
uint32_t seqId: 2;
uint32_t filler: 6;
uint32_t markers: 8;
uint32_t filler2: 16;
uint32_t txGain: 24;
uint32_t filler3: 8;
uint32_t ppsOffset;
uint64_t timestamp;
} __attribute__((packed)) headerTx_t;
typedef struct {
uint64_t control;
uint16_t packetSeqNum;
uint16_t packetSz;
uint8_t RXen : 1;
uint8_t TXen: 1;
uint8_t ADCsync : 1;
uint8_t DACsync : 1;
uint8_t TXhfull: 1;
uint8_t TXlate : 1;
uint8_t TXseqerr : 1;
uint8_t filler : 1;
uint8_t InbandReadAddr;
uint16_t InbandReadValue;
uint32_t ppsOffset: 30;
uint32_t ppsAlive: 1;
uint32_t gpsLock: 1;
uint64_t timestamp;
} __attribute__((packed)) headerRx_t;
typedef struct {
uint16_t control1;
uint8_t control2;
uint8_t atomicPacket: 1;
uint8_t timerOverflow: 1;
uint8_t filler: 6;
} __attribute__((packed)) footer_t;
typedef struct {
headerRx_t h;
c16_t b[READ_BLOCK_NB_SAMPLES];
footer_t f;
} __attribute__((packed)) rx_packet_t;
typedef struct {
headerTx_t h;
c16_t b[WRITE_BLOCK_NB_SAMPLES];
// footer_t f;
} __attribute__((packed)) tx_packet_t;
static inline void dumpHD(std::string ctx, headerRx_t h)
{
printf("header dump, %s\n", ctx.c_str());
uint8_t *z = (uint8_t *)&h;
for (uint i = 0; i < sizeof(headerRx_t); i++)
printf(" %02x:%02x %02x:%02x\n", z[i * 4 + 0], z[i * 4 + 1], z[i * 4 + 2], z[i * 4 + 3]);
printf(
"decoded magic: %lx\n"
" dataSz(words): %u, packetSeq:%u\n"
" InbandReadValue %x, InbandReadAddr:%x, TXhfull %u, DACsync %u, ADCsync: %u, TXen:%u, RXen:%u\n"
" gpslocked %d, ppsalive %d, pps offset %d\n"
" timestamp: %lu\n",
h.control,
h.packetSz,
h.packetSeqNum,
h.InbandReadValue,
h.InbandReadAddr,
h.TXhfull,
h.DACsync,
h.ADCsync,
h.TXen,
h.RXen,
h.gpsLock,
h.ppsAlive,
h.ppsOffset,
h.timestamp);
}
typedef struct {
int fd_write;
bool first_tx;
bool continuous_tx;
int64_t tx_count;
openair0_timestamp_t tx_ts;
uint txSeq;
tx_packet_t *tx_block;
uint8_t *tx_block_pos;
uint tx_block_num;
TSQueue<tx_packet_t *> *ready_tx;
} tx_thr_t;
typedef struct {
int fd_read;
int nb_blocks_per_read;
openair0_timestamp_t rx_timestamp;
openair0_timestamp_t rx_ts_interface;
int64_t rx_count;
uint lastPpsOffset;
int remain_samples;
uint64_t gap;
uint timerOverflow;
uint seqNum;
uint txLate;
uint txErr;
uint atomicPacket;
TSQueue<rx_packet_t *> *read_queue;
rx_packet_t *rx_live;
} rx_thr_t;
typedef struct {
char filename_write[FILENAME_MAX];
char filename_read[FILENAME_MAX];
int wait_for_first_pps;
rx_thr_t rx;
tx_thr_t tx;
std::atomic<bool> txHfull;
TSQueue<uint64_t> *last_rx;
} oc_state_t;
typedef struct {
openair0_device_t *rfdevice;
int antennas;
int dft_sz;
} threads_t;
static int check_ref_locked(oc_state_t *s)
{
return 0;
}
static int sync_to_gps(openair0_device_t *device)
{
return 0;
}
// INSTEAD OF TIMESTAMP AHEAD you need to READ the TXhfull flag from the RX packet header
// IF TXhfull FLAG = 0 then you can write 4 packets of 8192 samples ELSE You cannot
void *write_thread(void *arg)
{
oc_state_t *s = (oc_state_t *)arg;
FILE* fd=fopen("/tmp/headers", "w");
fprintf(fd,"time before call xdma, nano sec in xdma write, packet seq num, timestamp\n");
char * log_headers=getenv("LOGHEADERS");
// tx_packet_t ref;
// int seq=0;
uint64_t ts = 0;
tx_thr_t *tx = &s->tx;
bool do_rx = getenv("FAKE_RX") == NULL;
do {
tx_packet_t *p = tx->ready_tx->pop();
if (do_rx && s->txHfull) {
do {
struct timespec b, e;
clock_gettime(CLOCK_REALTIME, &b);
uint64_t last_rx;
do {
last_rx = s->last_rx->pop();
} while (last_rx + tx_ahead_max < p->h.timestamp);
clock_gettime(CLOCK_REALTIME, &e);
LOG_D(HW,
"tx buffer was half full, blocked for %ld ns, rx ts: %lu, tx t: %lu\n",
(e.tv_sec - b.tv_sec) * 1000 * 1000 * 1000 + e.tv_nsec - b.tv_nsec,
last_rx,
p->h.timestamp);
} while (s->txHfull);
} else
LOG_D(HW, "no need to wait tx ts: %lu\n", p->h.timestamp);
/*
// this is test code to repeat same packet forever with continuous tested timestamp
if (!seq)
memcpy(&ref,p, sizeof(ref));
for (int i=0; i<NB_BLOCKS_PER_WRITE; i++) {
memcpy(&p[i].b, ref.b, sizeof(ref.b));
if (ts!=p[i].h.timestamp)
printf("ERROROROROROO\n");
ts=p[i].h.timestamp+WRITE_BLOCK_NB_SAMPLES;
}*/
struct timespec b, e;
clock_gettime(CLOCK_REALTIME,&b);
uint8_t *j = (uint8_t *)p;
for (int i = 0; i < NB_BLOCKS_PER_WRITE; i++) {
tx_packet_t *cur = (tx_packet_t *)j;
if (ts != cur->h.timestamp && tx->continuous_tx)
LOG_E(HW, "tx is not contiguous\n");
ts = cur->h.timestamp + cur->h.packetSz;
/*
int sz=sizeof(cur->h.timestamp)*8;
float sign[sz]={};
c16_t* rx=cur->b;
for (int i=0; i<cur->h.packetSz/8; i++){
int bit=i%sz;
for (int j=0; j<8; j++)
sign[bit]+=rx[i*8+j].r*rx[i*8+j].r+rx[i*8+j].i*rx[i*8+j].i;
}
float total=0;
for (int i = 0; i < sz; i++)
total+=sign[i];
total/=sz;
uint64_t encoded_ts=0;
for (int i = 0; i < sz; i++)
if (sign[i] > total)
encoded_ts|=1ULL<<i;
printf("driver diff encoded versus header %ld\n", cur->h.timestamp - (int64_t)encoded_ts);
*/
j += sizeof(headerTx_t) + cur->h.packetSz * sizeof(*cur->b);
}
uint sz_bytes = j - (uint8_t *)p;
size_t wrote = write(tx->fd_write, p, sz_bytes);
clock_gettime(CLOCK_REALTIME,&e);
if (wrote != sz_bytes)
LOG_E(HW, "write to SDR failed, request: %u, wrote %ld\n", sz_bytes, wrote);
if (wrote < 0)
LOG_E(HW, "write to %s failed, errno %d:%s\n", s->filename_write, errno, strerror(errno));
LOG_D(HW, "wrote: for ts %lu, total size: %u\n", p->h.timestamp, sz_bytes);
if (log_headers) {
char str[60];
memset(str,' ', sizeof(str));
snprintf(str,sizeof(str), "%lu.%lu, %lu, %u, %lu\n", b.tv_sec, b.tv_nsec,(e.tv_sec-b.tv_sec)*1000*1000*1000+e.tv_nsec-b.tv_nsec,p->h.packetSeqNum,p->h.timestamp);
fwrite(str, sizeof(str),1,fd);
}
free(p);
} while (true);
return NULL;
}
struct energy_s {
float mean;
float papr_dB;
float peak;
};
struct energy_s compute_papr_db(const c16_t *x, size_t N)
{
float p_max = 0.0f;
float p_sum = 0.0f;
for (size_t n = 0; n < N; n++) {
float p = (float)x[n].r * x[n].r + (float)x[n].i * x[n].i;
p_sum += p;
if (p > p_max)
p_max = p;
}
float p_avg = p_sum / N;
float papr = p_max / p_avg;
return (struct energy_s){p_avg, 10.0f * log10f(papr), sqrt(p_max)};
}
static int32_t signalEnergy(c16_t *input, uint32_t length)
{
// init
simde__m128 mm0 = simde_mm_setzero_ps();
// Acc
for (uint32_t i = 0; i < (length >> 2); i++) {
simde__m128i in = simde_mm_loadu_si128((simde__m128i *)input);
mm0 = simde_mm_add_ps(mm0, simde_mm_cvtepi32_ps(simde_mm_madd_epi16(in, in)));
input += 4;
}
// leftover
float leftover_sum = 0;
c16_t *leftover_input = input + (length & ~3);
uint16_t lefover_count = length & 3;
for (int32_t i = 0; i < lefover_count; i++) {
leftover_sum += leftover_input[i].r * leftover_input[i].r + leftover_input[i].i * leftover_input[i].i;
}
// Ave
float sums[4];
simde_mm_store_ps(sums, mm0);
return (uint32_t)((sums[0] + sums[1] + sums[2] + sums[3] + leftover_sum) / (float)length);
}
#define BURST_NUM_OF_PACKETS (1u)
// DC-filter: 0 will be done in FPGA after seeing 128-consecutive samples having the same value
static inline int write_block(tx_thr_t *tx, c16_t *samples, uint sz, bool no_scaling)
{
// TO BE REWRITTEN BY LAURENT THOMAS
static uint stream_seqId = 0x01;
if (BURST_NUM_OF_PACKETS == 1) {
stream_seqId = 0x01; // Start of Burst all the time
} else if (((tx->tx_count) % BURST_NUM_OF_PACKETS) == 0) {
stream_seqId = 0x01; // Start of Burst
} else {
stream_seqId = 0x02; // Middle of Burst
}
if (!tx->tx_block) {
tx->tx_block = (tx_packet_t *)malloc16(NB_BLOCKS_PER_WRITE * sizeof(tx_packet_t));
tx->tx_block_pos = (uint8_t *)tx->tx_block;
}
// LOG_I(HW, "add tx packet for %u samples, ts %lu\n", sz,tx->tx_ts);
tx_packet_t *ant0 = (tx_packet_t *)tx->tx_block_pos;
ant0->h = (headerTx_t){.control = magic_tx,
.packetSeqNum = tx->txSeq++,
.packetSz = sz,
.seqId = stream_seqId,
.filler = 0x02,
.markers = 0xb1,
.filler2 = 0xabcd,
.txGain = 0x112233,
.filler3 = 0xf0,
.ppsOffset = 0x28272625,
.timestamp = (uint64_t)tx->tx_ts};
if (no_scaling)
memcpy(ant0->b, samples, sz * sizeof(c16_t));
else
for (uint i = 0; i < sz; i++)
ant0->b[i] = (c16_t){(int16_t)(samples[i].r<<4), (int16_t)(samples[i].i<<4)};
tx->tx_ts += sz;
tx->tx_block_pos += sizeof(headerTx_t) + sz * sizeof(*ant0->b);
tx->tx_block_num++;
tx->tx_count++;
if (tx->tx_block_num == NB_BLOCKS_PER_WRITE) {
tx->ready_tx->push(tx->tx_block);
tx->tx_block_num = 0;
tx->tx_block_pos = NULL;
tx->tx_block = NULL;
}
return sz;
}
static int oc_write(openair0_device_t *device, openair0_timestamp_t timestamp, void **buff, int nsamps, int cc, int flags)
{
tx_thr_t *tx = &((oc_state_t *)device->priv)->tx;
timestamp -= device->openair0_cfg->command_line_sample_advance + device->openair0_cfg->tx_sample_advance;
if (tx->first_tx) {
tx->tx_ts = timestamp;
tx->first_tx = false;
}
int64_t gap = timestamp - tx->tx_ts;
if (gap < 0) {
LOG_E(HW, "out of sequence\n");
gap = 0;
}
if (gap)
LOG_I(HW, "gap of %ld\n", gap);
else
LOG_D(HW, ".\n");
int wr_sz = nsamps;
// LOG_E(HW, "ask to write %d\n", wr_sz);
while (wr_sz > 0) {
int tmp = std::min(wr_sz, WRITE_BLOCK_NB_SAMPLES);
int sz = write_block(tx, ((c16_t *)buff[0]) + nsamps - wr_sz, tmp, device->openair0_cfg->num_rb_dl == -1);
if (sz != tmp)
LOG_E(HW, "ask to write %d, res is %d\n", tmp, sz);
wr_sz -= sz;
}
if (tx->tx_ts != timestamp + nsamps)
LOG_E(HW,"tx samples count error\n");
tx->tx_ts = timestamp + nsamps;
#if 0
static uint nsamps0 = 0;
struct energy_s e = compute_papr_db((c16_t *)*buff, nsamps);
if (e.mean > 1) {
LOG_I(HW, "sent power: %f, papr %f, max %f after %d samples with no energy\n", e.mean, e.papr_dB, e.peak, nsamps0);
nsamps0 = 0;
} else {
nsamps0 += nsamps;
}
#endif
return nsamps;
}
static void initial_block_align(rx_thr_t *rx)
{
LOG_I(HW, "Synchronizing rx\n");
__attribute__((aligned(32))) uint32_t b[sizeof(rx_packet_t)];
ssize_t ret = read(rx->fd_read, b, sizeof(b));
if (ret != sizeof(b)) {
LOG_E(HW, "Error reading %ld bytes: %ld (%s)\n", sizeof(b), ret, strerror(errno));
usleep(10000);
return;
}
uint i;
headerRx_t *rxh = NULL;
for (i = 0; i < sizeof(b) / sizeof(*b) - sizeof(headerRx_t); i++)
if (b[i] == (magic_rx & UINT32_MAX) && b[i + 1] == ((magic_rx >> 32) & UINT32_MAX)) {
LOG_D(HW, "found first magic at %d \n", i);
rxh = (headerRx_t *)(b + i);
dumpHD("first header:", *rxh);
break;
}
if (i == (sizeof(b) / sizeof(*b) - sizeof(headerRx_t))) {
LOG_E(HW, "%%error magic not found\n");
return;
}
ret = read(rx->fd_read, b, i * sizeof(*b));
rx->seqNum = rxh->packetSeqNum + 1;
rx->rx_timestamp = (int64_t)rxh->timestamp + rxh->packetSz;
rx->rx_ts_interface = rxh->timestamp;
rx->rx_count = 1;
}
static bool get_blocks(oc_state_t *s, rx_packet_t *p)
{
rx_thr_t *rx = &s->rx;
static struct timespec last_second={}, origin={};
static struct timespec now={};
static uint64_t tot_samples= 0;
int readSz = sizeof(*rx->rx_live) * rx->nb_blocks_per_read;
ssize_t ret = read(rx->fd_read, p, readSz);
if (ret != readSz || p[0].h.control != magic_rx) {
LOG_E(HW, "Error reading header asked for %d bytes, got %ld, magic: %lx\n", readSz, ret, p[0].h.control);
dumpHD("lost good header:", p[0].h);
rx->rx_count = -1;
return false;
}
clock_gettime(CLOCK_REALTIME, &now);
if ( last_second.tv_sec==0) {
last_second=now;
origin=now;
}
tot_samples += rx->nb_blocks_per_read * sizeof(p->b) / sizeof(*p->b);
if (now.tv_sec != last_second.tv_sec) {
LOG_I(HW,
"driver avg read rate:%f\n errors during last second: %s %u, %s %u, %s %u, %s %u, present "
"tx seq %u\n\n ",
(float)tot_samples / (now.tv_sec * 1000000 - origin.tv_sec * 1000000 + now.tv_nsec / 1000.0 - origin.tv_nsec / 1000.0),
rx->txLate ? "\x1B[93m"
"txLate"
"\x1B[0m"
: "txLate",
rx->txLate,
rx->txErr ? "\x1B[93m"
"txSeqerr"
"\x1B[0m"
: "txSeqerr",
rx->txErr,
rx->timerOverflow ? "\x1B[93m"
"timerOverflow"
"\x1B[0m"
: "timerOverflow",
rx->timerOverflow,
rx->atomicPacket ? "\x1B[93m"
"atomicPacket"
"\x1B[0m"
: "atomicPacket",
rx->atomicPacket,
s->tx.txSeq);
rx->txLate = rx->txErr = rx->timerOverflow = rx->atomicPacket = 0;
last_second.tv_sec++;
}
for (int i = 0; i < rx->nb_blocks_per_read; i++) {
if (rx->rx_timestamp != (int64_t)p[i].h.timestamp)
LOG_W(HW,
"expected ts: %lu got %lu, diff %ld, seq num %d, atomicPacket %d, timerOverflow %d\n",
rx->rx_timestamp,
p[i].h.timestamp,
(int64_t)p[i].h.timestamp - rx->rx_timestamp,
rx->seqNum,
(~p[i].f.atomicPacket & 0x01),
p[i].f.timerOverflow);
rx->rx_timestamp = p[i].h.timestamp + p[i].h.packetSz;
/*
if (llabs((int64_t)rx->lastPpsOffset - (int64_t)p[i].h.ppsOffset) > 12)
// pps_in is based on a real pulse from the GPS. It may have some jitter and drift during time.
// Thus it is normal to have some diff between theoretical (expected) and real (measured) values.
// >12 ->l means that we flag errors higher than +/- 0.1ppm.
// When the board's FPGA starts, the vcxo is not yet discplined to the GPS, thus we may observe
// some errors until the frequency error algorithm converges
printf("expected pps offset %u got %u, diff %d, seq num %d\n",
rx->lastPpsOffset,
p[i].h.ppsOffset,
p[i].h.ppsOffset- rx->lastPpsOffset,
rx->seqNum);
rx->lastPpsOffset = (p[i].h.ppsOffset + READ_BLOCK_NB_SAMPLES )% 122880000 ;
if (!p[i].h.ppsAlive)
printf("pps not alive\n");
*/
if (p[i].h.TXlate) {
LOG_D(HW, "TXlate\n");
rx->txLate++;
}
if (p[i].h.TXseqerr) {
LOG_W(HW, "TXseqerr, current tx seq is %u\n", s->tx.txSeq);
rx->txErr++;
}
if (p[i].f.control1 != magic_footer1 || p[i].f.control2 != magic_footer2)
LOG_W(HW, "footer error\n");
if (p[i].f.timerOverflow) {
LOG_D(HW, "timerOverflow\n");
rx->timerOverflow++;
}
if (p[i].f.atomicPacket) {
rx->atomicPacket++;
LOG_D(HW, "Not atomic\n");
}
static int last_filler = 0;
if (last_filler != p[i].f.filler)
LOG_I(HW, "filler changed to %x\n", p[i].f.filler);
last_filler = p[i].f.filler;
if (rx->seqNum % 65536 != p[i].h.packetSeqNum) {
LOG_W(HW,
"expected rx packet sequence number %u got %u, diff %d\n",
rx->seqNum,
p[i].h.packetSeqNum,
p[i].h.packetSeqNum - rx->seqNum);
rx->seqNum = p[i].h.packetSeqNum;
// rx->rx_count = -1;
// return false;
}
rx->seqNum++;
}
s->txHfull = p[rx->nb_blocks_per_read - 1].h.TXhfull;
if (!s->txHfull)
s->last_rx->push(rx->rx_timestamp);
LOG_D(HW, "read: %lu\n", rx->rx_timestamp);
return true;
}
void *read_thread(void *arg)
{
oc_state_t *s = (oc_state_t *)arg;
rx_thr_t *rx = &s->rx;
while (true) {
if (rx->rx_count == -1)
initial_block_align(rx);
if (rx->rx_count == -1) {
usleep(10);
continue;
}
if (rx->read_queue->m_queue.size() > 100) {
if (!getenv("FAKE_RX"))
LOG_W(HW, "rx consumer is too slow, trashing rx queue\n");
while (rx->read_queue->m_queue.size())
free(rx->read_queue->pop());
}
rx_packet_t *tmp = (rx_packet_t *)malloc(sizeof(*rx->rx_live) * rx->nb_blocks_per_read);
if (!get_blocks(s, tmp)) {
printf("getblocks returned bad\n");
free(tmp);
} else
rx->read_queue->push(tmp);
}
return NULL;
}
static int oc_read(openair0_device_t *device, openair0_timestamp_t *ptimestamp, void **buff, int nsamps, int cc)
{
rx_thr_t *rx = &((oc_state_t *)device->priv)->rx;
static int64_t reads_cnt = 0;
if (getenv("FAKE_RX") && reads_cnt > atoi(getenv("FAKE_RX"))) {
*ptimestamp = rx->rx_ts_interface;
rx->rx_ts_interface += nsamps;
return nsamps;
}
reads_cnt++;
c16_t **output=(c16_t**)buff;
int remain_to_get=nsamps;
while (remain_to_get > 0) {
while (rx->remain_samples > 0 && remain_to_get > 0) {
if (rx->gap) {
c16_t *out = output[0] + nsamps - remain_to_get;
while (rx->gap && remain_to_get) {
*out++ = {};
remain_to_get--;
rx->gap--;
rx->rx_ts_interface++;
}
}
rx_packet_t *last_rx = rx->rx_live;
int nb_samples_per_packet = sizeof(last_rx[0].b) / sizeof(last_rx[0].b[0]);
int nb_samples = nb_samples_per_packet * rx->nb_blocks_per_read;
int consumed_samples = nb_samples - rx->remain_samples;
int nb_consumed_samples_in_block= consumed_samples%nb_samples_per_packet;
int bloc = consumed_samples / nb_samples_per_packet;
rx_packet_t *cur_pkt = last_rx + bloc;
if (nb_consumed_samples_in_block == 0 && cur_pkt->h.timestamp != (uint64_t)rx->rx_ts_interface) {
rx->gap = cur_pkt->h.timestamp - rx->rx_ts_interface;
if (rx->gap > 0 && rx->gap < 1228800) {
LOG_W(HW, "gap of %ld, we fill \n", rx->gap);
continue;
} else {
LOG_W(HW, "gap of %ld, we break timestamp sequence %lu != %lu\n", rx->gap, rx->rx_ts_interface, cur_pkt->h.timestamp);
rx->rx_ts_interface = cur_pkt->h.timestamp;
rx->gap = 0;
}
}
int remaing_samples_in_block= nb_samples_per_packet-nb_consumed_samples_in_block;
int toCopy=std::min(remaing_samples_in_block,remain_to_get );
memcpy(output[0] + nsamps - remain_to_get, cur_pkt->b + nb_consumed_samples_in_block, toCopy * sizeof(cur_pkt->b[0]));
rx->remain_samples -= toCopy;
remain_to_get -= toCopy;
rx->rx_ts_interface += toCopy;
}
if( remain_to_get > 0 ) {
free(rx->rx_live);
rx->rx_live = rx->read_queue->pop();
rx->remain_samples = sizeof(rx->rx_live->b) * rx->nb_blocks_per_read / sizeof(*rx->rx_live->b);
}
}
*ptimestamp = rx->rx_ts_interface - nsamps;
return nsamps;
}
static int oc_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
// oc_state_t *s = (oc_state_t *)device->priv;
LOG_I(HW,
"Setting TX Freq %f, RX Freq %f, tune_offset: %f\n",
openair0_cfg[0].tx_freq[0],
openair0_cfg[0].rx_freq[0],
openair0_cfg[0].tune_offset);
return 0;
}
static int oc_set_gains(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
// oc_state_t *s = (oc_state_t *)device->priv;
LOG_I(HW, "Setting RX gain to %f \n", openair0_cfg[0].rx_gain[0]);
return 0;
}
static int oc_stop(openair0_device_t *device)
{
return 0;
}
void set_rx_gain_offset(openair0_config_t *openair0_cfg, int chain_index, int bw_gain_adjust)
{
int i = 0;
// loop through calibration table to find best adjustment factor for RX frequency
double min_diff = 6e9, diff, gain_adj = 0.0;
if (bw_gain_adjust == 1) {
switch ((int)openair0_cfg[0].sample_rate) {
case 46080000:
break;
case 30720000:
break;
case 23040000:
gain_adj = 1.25;
break;
case 15360000:
gain_adj = 3.0;
break;
case 7680000:
gain_adj = 6.0;
break;
case 3840000:
gain_adj = 9.0;
break;
case 1920000:
gain_adj = 12.0;
break;
default:
LOG_E(HW, "unknown sampling rate %d\n", (int)openair0_cfg[0].sample_rate);
// exit(-1);
break;
}
}
while (openair0_cfg->rx_gain_calib_table[i].freq > 0) {
diff = fabs(openair0_cfg->rx_freq[chain_index] - openair0_cfg->rx_gain_calib_table[i].freq);
LOG_I(HW,
"cal %d: freq %f, offset %f, diff %f\n",
i,
openair0_cfg->rx_gain_calib_table[i].freq,
openair0_cfg->rx_gain_calib_table[i].offset,
diff);
if (min_diff > diff) {
min_diff = diff;
openair0_cfg->rx_gain_offset[chain_index] = openair0_cfg->rx_gain_calib_table[i].offset + gain_adj;
}
i++;
}
}
static int oc_get_stats(openair0_device_t *device)
{
return (0);
}
static int oc_reset_stats(openair0_device_t *device)
{
return (0);
}
int oc_write_init(openair0_device_t *device)
{
LOG_E(HW, "trx_write_init should not be called, and is a design error even for USRP\n");
return -1;
}
static int oc_start(openair0_device_t *device)
{
oc_state_t *s = (oc_state_t *)device->priv;
s->rx.rx_count = -1;
s->wait_for_first_pps = 1;
s->tx.first_tx = true;
s->rx.nb_blocks_per_read = NB_BLOCKS_PER_READ;
s->rx.rx_live = (rx_packet_t *)malloc16(s->rx.nb_blocks_per_read * sizeof(*s->rx.rx_live));
s->tx.ready_tx = new TSQueue<tx_packet_t *>;
s->rx.read_queue = new TSQueue<rx_packet_t *>;
s->last_rx = new TSQueue<uint64_t>;
s->tx.fd_write = open(s->filename_write, O_WRONLY);
if (s->tx.fd_write < 0) {
LOG_E(HW, "Open %s failed, errno %d:%s\n", s->filename_write, errno, strerror(errno));
exit(1);
}
s->rx.fd_read = open(s->filename_read, O_RDONLY);
if (s->rx.fd_read < 0) {
LOG_E(HW, "Open %s failed, errno %d:%s\n", s->filename_read, errno, strerror(errno));
exit(1);
}
pthread_t w_thread;
threadCreate(&w_thread, write_thread, s, (char *)"write_thr", -1, OAI_PRIORITY_RT);
pthread_t r_thread;
threadCreate(&r_thread, read_thread, s, (char *)"read_thr", -1, OAI_PRIORITY_RT);
oc_set_gains(device, device->openair0_cfg);
oc_set_freq(device, device->openair0_cfg);
sync_to_gps(device);
check_ref_locked(s);
// Fixme: set sampling rate, lack of API in OAI
return 0;
}
static void oc_end(openair0_device_t *device)
{
if (device == NULL)
return;
// oc_state_t *s = (oc_state_t *)device->priv;
}
extern "C" {
int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
LOG_I(HW, "openair0_cfg->clock_source == '%d' (internal = %d, external = %d)\n", openair0_cfg->clock_source, internal, external);
oc_state_t *st;
if (device->priv == NULL) {
st = (oc_state_t *)calloc(1, sizeof(oc_state_t));
device->priv = st;
strcpy(st->filename_write, DEVICE_WRITE_DEFAULT);
strcpy(st->filename_read, DEVICE_READ_DEFAULT);
AssertFatal(st != NULL, "OC device: memory allocation failure\n");
st->tx.continuous_tx = openair0_cfg->duplex_mode == duplex_mode_FDD;
} else {
LOG_E(HW, "multiple calls to device init detected\n");
return 0;
}
device->openair0_cfg = openair0_cfg;
device->trx_start_func = oc_start;
device->trx_get_stats_func = oc_get_stats;
device->trx_reset_stats_func = oc_reset_stats;
device->trx_end_func = oc_end;
device->trx_stop_func = oc_stop;
device->trx_set_freq_func = oc_set_freq;
device->trx_set_gains_func = oc_set_gains;
device->trx_write_init = oc_write_init;
device->trx_write_func = oc_write;
device->trx_read_func = oc_read;
if (device->openair0_cfg->recplay_mode == RECPLAY_RECORDMODE) {
std::cerr << "OC device initialized in subframes record mode" << std::endl;
}
device->type = OC1_DEV;
struct {
int sample_rate;
int tx_sample_advance;
double tx_bw;
double rx_bw;
} config_table[] = {{245760000, 0, 200e6, 200e6},
{184320000, 0, 100e6, 100e6},
{122880000, 180, 80e6, 80e6},
{92160000, 0, 60e6, 60e6},
{61440000, 0, 40e6, 40e6},
{46080000, 0, 40e6, 40e6},
{30720000, 0, 40e6, 40e6},
{23040000, 0, 20e6, 20e6},
{15360000, 0, 10e6, 10e6},
{7680000, 0, 5e6, 5e6},
{1920000, 0, 1.25e6, 1.25e6}};
size_t i = 0;
openair0_cfg[0].sample_rate = 122880000; // only 122880000 is supported
for (; i < sizeofArray(config_table); i++)
if (config_table[i].sample_rate == (int)openair0_cfg[0].sample_rate) {
device->openair0_cfg->tx_sample_advance = config_table[i].tx_sample_advance;
device->openair0_cfg->tx_bw = config_table[i].tx_bw;
device->openair0_cfg->rx_bw = config_table[i].rx_bw;
break;
}
if (i == sizeofArray(config_table)) {
LOG_E(HW, "unknown sampling rate: %d\n", (int)openair0_cfg[0].sample_rate);
exit(-1);
}
return 0;
}
}