Commit 6925058e authored by laurent's avatar laurent

tools to measure per, paralleism trial

parent 3a376b65
...@@ -2383,6 +2383,7 @@ add_executable(ocpsoftmodem ...@@ -2383,6 +2383,7 @@ add_executable(ocpsoftmodem
${OPENAIR_TARGETS}/ARCH/COMMON/common_lib.c ${OPENAIR_TARGETS}/ARCH/COMMON/common_lib.c
${OPENAIR_DIR}/common/utils/utils.c ${OPENAIR_DIR}/common/utils/utils.c
${OPENAIR_DIR}/common/utils/system.c ${OPENAIR_DIR}/common/utils/system.c
${OPENAIR_DIR}/common/utils/threadPool/thread-pool.c
${GTPU_need_ITTI} ${GTPU_need_ITTI}
${T_SOURCE} ${T_SOURCE}
${CONFIG_SOURCES} ${CONFIG_SOURCES}
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <common/utils/threadPool/thread-pool.h> #include <common/utils/threadPool/thread-pool.h>
RAN_CONTEXT_t RC; RAN_CONTEXT_t RC;
tpool_t * Tpool;
volatile int oai_exit = 0; volatile int oai_exit = 0;
char rf_config_file[1024]=""; char rf_config_file[1024]="";
unsigned int mmapped_dma=0; unsigned int mmapped_dma=0;
...@@ -296,7 +297,11 @@ static inline int rxtx(PHY_VARS_gNB *gNB,gNB_L1_rxtx_proc_t *proc, char *thread_ ...@@ -296,7 +297,11 @@ static inline int rxtx(PHY_VARS_gNB *gNB,gNB_L1_rxtx_proc_t *proc, char *thread_
// (may be relaxed in the future for performance reasons) // (may be relaxed in the future for performance reasons)
// ***************************************** // *****************************************
//if (wait_CCs(proc)<0) return(-1); //if (wait_CCs(proc)<0) return(-1);
uint64_t a=rdtsc();
phy_procedures_gNB_TX(gNB, proc, 1); phy_procedures_gNB_TX(gNB, proc, 1);
uint64_t b=rdtsc() -a;
if (b/3500.0 > 100 )
printf("processin: %d, %ld \n", proc->slot_rx, b/3500);
return(0); return(0);
} }
...@@ -717,6 +722,26 @@ bool setup_RU_buffers(RU_t *ru) { ...@@ -717,6 +722,26 @@ bool setup_RU_buffers(RU_t *ru) {
return(true); return(true);
} }
static void modulateSend (void* arg) {
RU_t *ru=*(RU_t**)arg;
if(ru->num_eNB==0) {
// do TX front-end processing if needed (precoding and/or IDFTs)
if (ru->feptx_prec)
ru->feptx_prec(ru);
// do OFDM if needed
if ((ru->fh_north_asynch_in == NULL) && (ru->feptx_ofdm))
ru->feptx_ofdm(ru);
// do outgoing fronthaul (south) if needed
if ((ru->fh_north_asynch_in == NULL) && (ru->fh_south_out))
ru->fh_south_out(ru);
if (ru->fh_north_out)
ru->fh_north_out(ru);
}
}
static void *ru_thread( void *param ) { static void *ru_thread( void *param ) {
RU_t *ru = (RU_t *)param; RU_t *ru = (RU_t *)param;
RU_proc_t *proc = &ru->proc; RU_proc_t *proc = &ru->proc;
...@@ -729,12 +754,6 @@ static void *ru_thread( void *param ) { ...@@ -729,12 +754,6 @@ static void *ru_thread( void *param ) {
sprintf(threadname,"ru_thread %d",ru->idx); sprintf(threadname,"ru_thread %d",ru->idx);
LOG_I(PHY,"Starting RU %d (%s,%s),\n",ru->idx,NB_functions[ru->function],NB_timing[ru->if_timing]); LOG_I(PHY,"Starting RU %d (%s,%s),\n",ru->idx,NB_functions[ru->function],NB_timing[ru->if_timing]);
// Start IF device if any
if (ru->start_if) {
LOG_I(PHY,"Starting IF interface for RU %d\n",ru->idx);
AssertFatal(ru->start_if(ru,NULL) == 0, "Could not start the IF device\n");
AssertFatal(connect_rau(ru)==0,"Cannot connect to remote radio\n");
}
if (ru->if_south == LOCAL_RF) { // configure RF parameters only if (ru->if_south == LOCAL_RF) { // configure RF parameters only
fill_rf_config(ru,ru->rf_config_file); fill_rf_config(ru,ru->rf_config_file);
...@@ -764,8 +783,18 @@ static void *ru_thread( void *param ) { ...@@ -764,8 +783,18 @@ static void *ru_thread( void *param ) {
LOG_I(PHY, "wait main thread that RU %d is ready\n",ru->idx); LOG_I(PHY, "wait main thread that RU %d is ready\n",ru->idx);
delNotifiedFIFO_elt(pullNotifiedFIFO(&ruThreadFIFO)); delNotifiedFIFO_elt(pullNotifiedFIFO(&ruThreadFIFO));
// This is a forever while loop, it loops over subframes which are scheduled by incoming samples from HW devices // Start IF device if any
if (ru->start_if) {
LOG_I(PHY,"Starting IF interface for RU %d\n",ru->idx);
AssertFatal(ru->start_if(ru,NULL) == 0, "Could not start the IF device\n");
AssertFatal(connect_rau(ru)==0,"Cannot connect to remote radio\n");
}
// This is a forever while loop, it loops over subframes which are scheduled by incoming samples from HW devices
initRefTimes(rx);
initRefTimes(frx);
initRefTimes(mainProc);
while (!oai_exit) { while (!oai_exit) {
// these are local subframe/frame counters to check that we are in synch with the fronthaul timing. // these are local subframe/frame counters to check that we are in synch with the fronthaul timing.
// They are set on the first rx/tx in the underly FH routines. // They are set on the first rx/tx in the underly FH routines.
...@@ -777,6 +806,7 @@ static void *ru_thread( void *param ) { ...@@ -777,6 +806,7 @@ static void *ru_thread( void *param ) {
slot++; slot++;
} }
pickTime(beg);
// synchronization on input FH interface, acquire signals/data and block // synchronization on input FH interface, acquire signals/data and block
AssertFatal(ru->fh_south_in, "No fronthaul interface at south port"); AssertFatal(ru->fh_south_in, "No fronthaul interface at south port");
ru->fh_south_in(ru,&frame,&slot); ru->fh_south_in(ru,&frame,&slot);
...@@ -801,35 +831,32 @@ static void *ru_thread( void *param ) { ...@@ -801,35 +831,32 @@ static void *ru_thread( void *param ) {
if (ru->idx!=0) if (ru->idx!=0)
proc->frame_tx = (proc->frame_tx+proc->frame_offset)&1023; proc->frame_tx = (proc->frame_tx+proc->frame_offset)&1023;
updateTimes(beg, &rx, 1000, "trx_read");
pickTime(beg2);
if (rx.iterations%1000 == 0 ) printf("%d \n", rx.iterations);
// do RX front-end processing (frequency-shift, dft) if needed // do RX front-end processing (frequency-shift, dft) if needed
if (ru->feprx) if (ru->feprx)
ru->feprx(ru); ru->feprx(ru);
// At this point, all information for subframe has been received on FH interface // At this point, all information for subframe has been received on FH interface
updateTimes(beg2, &frx, 1000, "feprx");
pickTime(beg3);
// wakeup all gNB processes waiting for this RU // wakeup all gNB processes waiting for this RU
for (int gnb=0; gnb < ru->num_gNB; gnb++) for (int gnb=0; gnb < ru->num_gNB; gnb++)
ru->gNB_top(ru->gNB_list[gnb],ru->proc.frame_rx,ru->proc.tti_rx,"not def",ru); ru->gNB_top(ru->gNB_list[gnb],ru->proc.frame_rx,ru->proc.tti_rx,"not def",ru);
if(ru->num_eNB==0) { updateTimes(beg3, &mainProc, 1000, "rxtx");
// do TX front-end processing if needed (precoding and/or IDFTs)
if (ru->feptx_prec) notifiedFIFO_elt_t *txWork=newNotifiedFIFO_elt(0,0,NULL,modulateSend);
ru->feptx_prec(ru); void **tmp= (void**)NotifiedFifoData(txWork);
*tmp=(void*)ru;
// do OFDM if needed pushTpool(Tpool,txWork);
if ((ru->fh_north_asynch_in == NULL) && (ru->feptx_ofdm))
ru->feptx_ofdm(ru);
// do outgoing fronthaul (south) if needed
if ((ru->fh_north_asynch_in == NULL) && (ru->fh_south_out))
ru->fh_south_out(ru);
if (ru->fh_north_out)
ru->fh_north_out(ru);
}
} }
notifiedFIFO_elt_t *msg2=newNotifiedFIFO_elt(0,AbortRU,NULL,NULL); notifiedFIFO_elt_t *msg2=newNotifiedFIFO_elt(sizeof(ru),AbortRU,NULL,modulateSend);
pushNotifiedFIFO(&mainThreadFIFO,msg2); pushNotifiedFIFO(&mainThreadFIFO,msg2);
return NULL; return NULL;
} }
...@@ -1264,6 +1291,7 @@ void init_RU(const char *rf_config_file) { ...@@ -1264,6 +1291,7 @@ void init_RU(const char *rf_config_file) {
LOG_D(HW,"[nr-softmodem.c] RU threads created\n"); LOG_D(HW,"[nr-softmodem.c] RU threads created\n");
} }
int main( int argc, char **argv ) { int main( int argc, char **argv ) {
AssertFatal(load_configmodule(argc,argv) != NULL, ""); AssertFatal(load_configmodule(argc,argv) != NULL, "");
logInit(); logInit();
...@@ -1289,6 +1317,11 @@ int main( int argc, char **argv ) { ...@@ -1289,6 +1317,11 @@ int main( int argc, char **argv ) {
pthread_cond_init(&sync_cond,NULL); pthread_cond_init(&sync_cond,NULL);
pthread_mutex_init(&sync_mutex, NULL); pthread_mutex_init(&sync_mutex, NULL);
tpool_t pool;
Tpool=&pool;
char params[]="-1,-1";
initTpool(params, Tpool, false);
if (do_forms==1) { if (do_forms==1) {
loader_shlibfunc_t shlib_fdesc[1]= {0}; loader_shlibfunc_t shlib_fdesc[1]= {0};
shlib_fdesc[0].fname="startScope"; shlib_fdesc[0].fname="startScope";
...@@ -1310,16 +1343,16 @@ int main( int argc, char **argv ) { ...@@ -1310,16 +1343,16 @@ int main( int argc, char **argv ) {
for (int ru=0; ru<RC.nb_RU; ru++) for (int ru=0; ru<RC.nb_RU; ru++)
msgs[ru]=pullNotifiedFIFO(&mainThreadFIFO); msgs[ru]=pullNotifiedFIFO(&mainThreadFIFO);
init_eNB_afterRU(); init_eNB_afterRU();
for (int ru=0; ru<RC.nb_RU; ru++) for (int ru=0; ru<RC.nb_RU; ru++)
pushNotifiedFIFO(msgs[ru]->reponseFifo, msgs[ru]); pushNotifiedFIFO(msgs[ru]->reponseFifo, msgs[ru]);
pthread_mutex_lock(&sync_mutex); pthread_mutex_lock(&sync_mutex);
sync_var=0; sync_var=0;
pthread_cond_broadcast(&sync_cond); pthread_cond_broadcast(&sync_cond);
pthread_mutex_unlock(&sync_mutex); pthread_mutex_unlock(&sync_mutex);
// When threads leaves, they send a final message to main // When threads leaves, they send a final message to main
notifiedFIFO_elt_t *msg=pullNotifiedFIFO(&mainThreadFIFO); notifiedFIFO_elt_t *msg=pullNotifiedFIFO(&mainThreadFIFO);
return 0; return 0;
......
...@@ -112,7 +112,6 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO){ ...@@ -112,7 +112,6 @@ void nr_schedule_response(NR_Sched_Rsp_t *Sched_INFO){
for (i=0;i<number_dl_pdu;i++) { for (i=0;i<number_dl_pdu;i++) {
dl_config_pdu = &DL_req->dl_config_request_body.dl_config_pdu_list[i]; dl_config_pdu = &DL_req->dl_config_request_body.dl_config_pdu_list[i];
LOG_D(PHY,"NFAPI: dl_pdu %d : type %d\n",i,dl_config_pdu->pdu_type); LOG_D(PHY,"NFAPI: dl_pdu %d : type %d\n",i,dl_config_pdu->pdu_type);
printf("NFAPI: dl_pdu %d : type %d\n",i,dl_config_pdu->pdu_type);
switch (dl_config_pdu->pdu_type) { switch (dl_config_pdu->pdu_type) {
case NFAPI_NR_DL_CONFIG_BCH_PDU_TYPE: case NFAPI_NR_DL_CONFIG_BCH_PDU_TYPE:
AssertFatal(dl_config_pdu->bch_pdu_rel15.pdu_index < TX_req->tx_request_body.number_of_pdus, AssertFatal(dl_config_pdu->bch_pdu_rel15.pdu_index < TX_req->tx_request_body.number_of_pdus,
......
...@@ -143,18 +143,25 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB, ...@@ -143,18 +143,25 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB,
if ((cfg->subframe_config.duplex_mode.value == TDD) && (nr_slot_select(cfg,slot)==SF_UL)) return; if ((cfg->subframe_config.duplex_mode.value == TDD) && (nr_slot_select(cfg,slot)==SF_UL)) return;
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_ENB_TX+offset,1); VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_ENB_TX+offset,1);
initRefTimes(mem);
initRefTimes(sign);
initRefTimes(dci);
initRefTimes(pdsch);
if (do_meas==1) start_meas(&gNB->phy_proc_tx); if (do_meas==1) start_meas(&gNB->phy_proc_tx);
pickTime(beg);
// clear the transmit data array for the current subframe // clear the transmit data array for the current subframe
for (aa=0; aa<1/*15*/; aa++) { for (aa=0; aa<1/*15*/; aa++) {
memset(gNB->common_vars.txdataF[aa],0,fp->samples_per_slot_wCP*sizeof(int32_t)); memset(gNB->common_vars.txdataF[aa],0,fp->samples_per_slot_wCP*sizeof(int32_t));
} }
updateTimes(beg, &mem, 1000, "emem");
pickTime(beg2);
if (nfapi_mode == 0 || nfapi_mode == 1) { if (nfapi_mode == 0 || nfapi_mode == 1) {
nr_common_signal_procedures(gNB,frame, slot); nr_common_signal_procedures(gNB,frame, slot);
//if (frame == 9) //if (frame == 9)
//write_output("txdataF.m","txdataF",gNB->common_vars.txdataF[aa],fp->samples_per_frame_wCP, 1, 1); //write_output("txdataF.m","txdataF",gNB->common_vars.txdataF[aa],fp->samples_per_frame_wCP, 1, 1);
updateTimes(beg2, &sign, 1000, "sign");
} }
num_dci = gNB->pdcch_vars.num_dci; num_dci = gNB->pdcch_vars.num_dci;
...@@ -165,18 +172,23 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB, ...@@ -165,18 +172,23 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB,
Calling nr_generate_dci_top (number of DCI %d)\n", gNB->Mod_id, frame, slot, num_dci); Calling nr_generate_dci_top (number of DCI %d)\n", gNB->Mod_id, frame, slot, num_dci);
if (nfapi_mode == 0 || nfapi_mode == 1) { if (nfapi_mode == 0 || nfapi_mode == 1) {
pickTime(beg3);
nr_generate_dci_top(gNB->pdcch_vars, nr_generate_dci_top(gNB->pdcch_vars,
gNB->nr_gold_pdcch_dmrs[slot], gNB->nr_gold_pdcch_dmrs[slot],
gNB->common_vars.txdataF[0], gNB->common_vars.txdataF[0],
AMP, *fp, *cfg); AMP, *fp, *cfg);
updateTimes(beg3, &dci, 1000, "dic");
pickTime(beg4);
if (num_pdsch_rnti) { if (num_pdsch_rnti) {
LOG_I(PHY, "PDSCH generation started (%d)\n", num_pdsch_rnti); LOG_I(PHY, "PDSCH generation started slot %d (%d)\n", slot, num_pdsch_rnti);
nr_generate_pdsch(*gNB->dlsch[0][0], nr_generate_pdsch(*gNB->dlsch[0][0],
gNB->pdcch_vars.dci_alloc[0], gNB->pdcch_vars.dci_alloc[0],
gNB->nr_gold_pdsch_dmrs[slot], gNB->nr_gold_pdsch_dmrs[slot],
gNB->common_vars.txdataF, gNB->common_vars.txdataF,
AMP, slot, *fp, *cfg); AMP, slot, *fp, *cfg);
updateTimes(beg4, &pdsch, 1000, "pdsch");
} }
} }
} }
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#define byteToSample(a,b) ((a)/(sizeof(sample_t)*(b))) #define byteToSample(a,b) ((a)/(sizeof(sample_t)*(b)))
#define sample_t uint32_t //2*16 bits complex number #define sample_t uint32_t //2*16 bits complex number
pthread_mutex_t Sockmutex;
typedef struct buffer_s { typedef struct buffer_s {
int conn_sock; int conn_sock;
...@@ -212,6 +213,7 @@ sin_addr: ...@@ -212,6 +213,7 @@ sin_addr:
uint64_t lastW=-1; uint64_t lastW=-1;
int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, int nsamps, int nbAnt, int flags) { int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, int nsamps, int nbAnt, int flags) {
pthread_mutex_lock(&Sockmutex);
rfsimulator_state_t *t = device->priv; rfsimulator_state_t *t = device->priv;
LOG_D(HW,"sending %d samples at time: %ld\n", nsamps, timestamp); LOG_D(HW,"sending %d samples at time: %ld\n", nsamps, timestamp);
...@@ -237,25 +239,30 @@ int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, voi ...@@ -237,25 +239,30 @@ int rfsimulator_write(openair0_device *device, openair0_timestamp timestamp, voi
lastW=timestamp; lastW=timestamp;
LOG_D(HW,"sent %d samples at time: %ld->%ld, energy in first antenna: %d\n", LOG_D(HW,"sent %d samples at time: %ld->%ld, energy in first antenna: %d\n",
nsamps, timestamp, timestamp+nsamps, signal_energy(samplesVoid[0], nsamps) ); nsamps, timestamp, timestamp+nsamps, signal_energy(samplesVoid[0], nsamps) );
// Let's verify we don't have incoming data // Let's verify we don't have incoming data
// This is mandatory when the opposite side don't transmit // This is mandatory when the opposite side don't transmit
flushInput(t, 0); flushInput(t, 0);
pthread_mutex_unlock(&Sockmutex);
return nsamps; return nsamps;
} }
static bool flushInput(rfsimulator_state_t *t, int timeout) { static bool flushInput(rfsimulator_state_t *t, int timeout) {
// Process all incoming events on sockets // Process all incoming events on sockets
// store the data in lists // store the data in lists
struct epoll_event events[FD_SETSIZE]= {0}; struct epoll_event events[FD_SETSIZE]= {0};
int nfds = epoll_wait(t->epollfd, events, FD_SETSIZE, timeout); int nfds = epoll_wait(t->epollfd, events, FD_SETSIZE, timeout);
if ( nfds==-1 ) { if ( nfds==-1 ) {
if ( errno==EINTR || errno==EAGAIN ) if ( errno==EINTR || errno==EAGAIN ) {
return false; return false;
}
else else
AssertFatal(false,"error in epoll_wait\n"); AssertFatal(false,"error in epoll_wait\n");
} }
for (int nbEv = 0; nbEv < nfds; ++nbEv) { for (int nbEv = 0; nbEv < nfds; ++nbEv) {
int fd=events[nbEv].data.fd; int fd=events[nbEv].data.fd;
...@@ -345,7 +352,6 @@ static bool flushInput(rfsimulator_state_t *t, int timeout) { ...@@ -345,7 +352,6 @@ static bool flushInput(rfsimulator_state_t *t, int timeout) {
} }
} }
} }
return nfds>0; return nfds>0;
} }
...@@ -354,6 +360,7 @@ int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, vo ...@@ -354,6 +360,7 @@ int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, vo
LOG_E(HW, "rfsimulator: only 1 antenna tested\n"); LOG_E(HW, "rfsimulator: only 1 antenna tested\n");
exit(1); exit(1);
} }
pthread_mutex_lock(&Sockmutex);
rfsimulator_state_t *t = device->priv; rfsimulator_state_t *t = device->priv;
LOG_D(HW, "Enter rfsimulator_read, expect %d samples, will release at TS: %ld\n", nsamps, t->nextTimestamp+nsamps); LOG_D(HW, "Enter rfsimulator_read, expect %d samples, will release at TS: %ld\n", nsamps, t->nextTimestamp+nsamps);
...@@ -374,6 +381,7 @@ int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, vo ...@@ -374,6 +381,7 @@ int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, vo
t->nextTimestamp+=nsamps; t->nextTimestamp+=nsamps;
LOG_W(HW,"Generated void samples for Rx: %ld\n", t->nextTimestamp); LOG_W(HW,"Generated void samples for Rx: %ld\n", t->nextTimestamp);
*ptimestamp = t->nextTimestamp-nsamps; *ptimestamp = t->nextTimestamp-nsamps;
pthread_mutex_unlock(&Sockmutex);
return nsamps; return nsamps;
} }
} else { } else {
...@@ -424,6 +432,7 @@ int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, vo ...@@ -424,6 +432,7 @@ int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimestamp, vo
nsamps, nsamps,
*ptimestamp, t->nextTimestamp, *ptimestamp, t->nextTimestamp,
signal_energy(samplesVoid[0], nsamps)); signal_energy(samplesVoid[0], nsamps));
pthread_mutex_unlock(&Sockmutex);
return nsamps; return nsamps;
} }
...@@ -460,12 +469,12 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { ...@@ -460,12 +469,12 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) {
set_log(TMR,OAILOG_DEBUG); set_log(TMR,OAILOG_DEBUG);
//set_log(PHY,OAILOG_DEBUG); //set_log(PHY,OAILOG_DEBUG);
rfsimulator_state_t *rfsimulator = (rfsimulator_state_t *)calloc(sizeof(rfsimulator_state_t),1); rfsimulator_state_t *rfsimulator = (rfsimulator_state_t *)calloc(sizeof(rfsimulator_state_t),1);
if ((rfsimulator->ip=getenv("RFSIMULATOR")) == NULL ) { if ((rfsimulator->ip=getenv("RFSIMULATOR")) == NULL ) {
LOG_E(HW,helpTxt); LOG_E(HW,helpTxt);
exit(1); exit(1);
} }
pthread_mutex_init(&Sockmutex, NULL);
if ( strncasecmp(rfsimulator->ip,"enb",3) == 0 || if ( strncasecmp(rfsimulator->ip,"enb",3) == 0 ||
strncasecmp(rfsimulator->ip,"server",3) == 0 ) strncasecmp(rfsimulator->ip,"server",3) == 0 )
rfsimulator->typeStamp = ENB_MAGICDL_FDD; rfsimulator->typeStamp = ENB_MAGICDL_FDD;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment