aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric <ewild@sysmocom.de>2022-06-06 00:48:09 +0200
committerEric <ewild@sysmocom.de>2022-07-11 20:33:37 +0200
commit935c8cb7c977d063f472a8be6b2b024593894b33 (patch)
tree0491d351924c0fb8b4674fa52a23b8b992922556
parentf590eeb436a208f2bb5bb931604dc18a243a6d7a (diff)
new mssynctoy
-rw-r--r--.gitignore6
-rw-r--r--GSM/GSMCommon.cpp1
-rw-r--r--GSM/GSMCommon.h1
-rw-r--r--Transceiver52M/Makefile.am75
-rw-r--r--Transceiver52M/Resampler.cpp2
-rw-r--r--Transceiver52M/Transceiver2.cpp124
-rw-r--r--Transceiver52M/grgsm_vitac/constants.h2
-rw-r--r--Transceiver52M/grgsm_vitac/grgsm_vitac.cpp277
-rw-r--r--Transceiver52M/grgsm_vitac/grgsm_vitac.h42
-rw-r--r--Transceiver52M/itrq.h204
-rw-r--r--Transceiver52M/l1if.cpp2
-rw-r--r--Transceiver52M/l1if.h24
-rw-r--r--Transceiver52M/ms/bladerf_specific.h482
-rw-r--r--Transceiver52M/ms/ms_commandhandler.cpp229
-rw-r--r--Transceiver52M/ms/ms_rx_burst.cpp211
-rw-r--r--Transceiver52M/ms/ms_rx_burst.h25
-rw-r--r--Transceiver52M/ms/ms_rx_lower.cpp330
-rw-r--r--Transceiver52M/ms/ms_rx_upper.cpp364
-rw-r--r--Transceiver52M/ms/ms_rx_upper.h121
-rw-r--r--Transceiver52M/ms/ms_state.h175
-rw-r--r--Transceiver52M/ms/syncthing.cpp332
-rw-r--r--Transceiver52M/ms/syncthing.h224
-rw-r--r--Transceiver52M/ms/uhd_specific.h274
-rw-r--r--Transceiver52M/sch.c11
-rw-r--r--Transceiver52M/sigProcLib.cpp163
-rw-r--r--Transceiver52M/sigProcLib.h3
-rw-r--r--trxcon/l1ctl.c7
-rw-r--r--trxcon/logging.c2
-rw-r--r--trxcon/trx_if.c102
-rw-r--r--trxcon/trx_if.h6
-rw-r--r--trxcon/trxcon.c17
31 files changed, 3519 insertions, 319 deletions
diff --git a/.gitignore b/.gitignore
index 0140f9b..79e1531 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,12 @@ Transceiver52M/osmo-trx-uhd
Transceiver52M/osmo-trx-usrp1
Transceiver52M/osmo-trx-lms
Transceiver52M/osmo-trx-ipc
+Transceiver52M/osmo-trx-blade
+Transceiver52M/osmo-trx-syncthing-blade
+Transceiver52M/osmo-trx-syncthing-uhd
+Transceiver52M/osmo-trx-ms-blade
+Transceiver52M/osmo-trx-ms-uhd
+
.clang-format
diff --git a/GSM/GSMCommon.cpp b/GSM/GSMCommon.cpp
index 59548b2..a9e2bb1 100644
--- a/GSM/GSMCommon.cpp
+++ b/GSM/GSMCommon.cpp
@@ -55,6 +55,7 @@ const BitVector GSM::gEdgeTrainingSequence[] = {
};
const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
+const BitVector GSM::gDummyBurstTSC("01110001011100010111000101");
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000"); /* GSM, GMSK (default) */
diff --git a/GSM/GSMCommon.h b/GSM/GSMCommon.h
index 386365a..aa059c2 100644
--- a/GSM/GSMCommon.h
+++ b/GSM/GSMCommon.h
@@ -52,6 +52,7 @@ extern const BitVector gEdgeTrainingSequence[];
/** C0T0 filler burst, GSM 05.02, 5.2.6 */
extern const BitVector gDummyBurst;
+extern const BitVector gDummyBurstTSC;
/** Random access burst synch. sequence */
extern const BitVector gRACHSynchSequenceTS0;
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 0699ffb..78a8a27 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -24,8 +24,8 @@ include $(top_srcdir)/Makefile.common
SUBDIRS = arch device
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common
-AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
-AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
+AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
noinst_LTLIBRARIES = libtransceiver_common.la
@@ -38,8 +38,6 @@ COMMON_SOURCES = \
radioBuffer.cpp \
sigProcLib.cpp \
signalVector.cpp \
- Transceiver.cpp \
- Transceiver2.cpp \
ChannelizerBase.cpp \
Channelizer.cpp \
Synthesis.cpp \
@@ -86,52 +84,47 @@ COMMON_LDADD = \
bin_PROGRAMS =
if DEVICE_UHD
-bin_PROGRAMS += osmo-trx-uhd
-osmo_trx_uhd_SOURCES = osmo-trx.cpp
-osmo_trx_uhd_LDADD = \
- $(builddir)/device/uhd/libdevice.la \
+bin_PROGRAMS += osmo-trx-ms-uhd
+osmo_trx_ms_uhd_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
+osmo_trx_ms_uhd_LDADD = \
+ $(builddir)/device/bladerf/libdevice.la \
$(COMMON_LDADD) \
$(UHD_LIBS) \
$(TRXCON_LA)
-osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
-endif
+osmo_trx_ms_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD
-if DEVICE_USRP1
-bin_PROGRAMS += osmo-trx-usrp1
-osmo_trx_usrp1_SOURCES = osmo-trx.cpp
-osmo_trx_usrp1_LDADD = \
- $(builddir)/device/usrp1/libdevice.la \
- $(COMMON_LDADD) \
- $(USRP_LIBS)
-osmo_trx_usrp1_CPPFLAGS = $(AM_CPPFLAGS) $(USRP_CFLAGS)
-endif
+bin_PROGRAMS += osmo-trx-syncthing-uhd
+osmo_trx_syncthing_uhd_SOURCES = $(osmo_trx_ms_uhd_SOURCES) ms/ms_rx_burst.cpp
+osmo_trx_syncthing_uhd_LDADD = $(osmo_trx_ms_uhd_LDADD)
+osmo_trx_syncthing_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DSYNCTHINGONLY -DBUILDUHD
+#osmo_trx_syncthing_LDFLAGS = -fsanitize=address,undefined -shared-libsan
-if DEVICE_LMS
-bin_PROGRAMS += osmo-trx-lms
-osmo_trx_lms_SOURCES = osmo-trx.cpp
-osmo_trx_lms_LDADD = \
- $(builddir)/device/lms/libdevice.la \
- $(COMMON_LDADD) \
- $(LMS_LIBS)
-osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS)
-endif
-
-if DEVICE_IPC
-bin_PROGRAMS += osmo-trx-ipc
-osmo_trx_ipc_SOURCES = osmo-trx.cpp
-osmo_trx_ipc_LDADD = \
- $(builddir)/device/ipc/libdevice.la \
- $(COMMON_LDADD)
-osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
endif
if DEVICE_BLADE
-bin_PROGRAMS += osmo-trx-blade
-osmo_trx_blade_SOURCES = osmo-trx.cpp
-osmo_trx_blade_LDADD = \
+bin_PROGRAMS += osmo-trx-ms-blade
+osmo_trx_ms_blade_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
+osmo_trx_ms_blade_LDADD = \
$(builddir)/device/bladerf/libdevice.la \
$(COMMON_LDADD) \
- $(BLADE_LIBS)
-osmo_trx_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS)
+ $(BLADE_LIBS) \
+ $(TRXCON_LA)
+osmo_trx_ms_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE
+
+bin_PROGRAMS += osmo-trx-syncthing-blade
+osmo_trx_syncthing_blade_SOURCES = $(osmo_trx_ms_blade_SOURCES) ms/ms_rx_burst.cpp
+osmo_trx_syncthing_blade_LDADD = $(osmo_trx_ms_blade_LDADD)
+osmo_trx_syncthing_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DSYNCTHINGONLY -DBUILDBLADE -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
+#osmo_trx_syncthing_LDFLAGS = -fsanitize=address,undefined -shared-libsan
endif
+
+noinst_HEADERS += \
+ ms/syncthing.h \
+ ms/bladerf_specific.h \
+ ms/uhd_specific.h \
+ ms/ms_rx_upper.h \
+ ms/ms_state.h \
+ itrq.h
+# -fsanitize=address,undefined -shared-libsan -O0
+#
diff --git a/Transceiver52M/Resampler.cpp b/Transceiver52M/Resampler.cpp
index 910c7ff..00d19ec 100644
--- a/Transceiver52M/Resampler.cpp
+++ b/Transceiver52M/Resampler.cpp
@@ -32,7 +32,7 @@ extern "C" {
#define M_PI 3.14159265358979323846264338327f
#endif
-#define MAX_OUTPUT_LEN 4096
+#define MAX_OUTPUT_LEN 4096 * 4
using namespace std;
diff --git a/Transceiver52M/Transceiver2.cpp b/Transceiver52M/Transceiver2.cpp
index 4430b83..983643b 100644
--- a/Transceiver52M/Transceiver2.cpp
+++ b/Transceiver52M/Transceiver2.cpp
@@ -26,6 +26,7 @@
#include <stdio.h>
#include <Logger.h>
#include "Transceiver2.h"
+#include <grgsm_vitac/grgsm_vitac.h>
extern "C" {
#include "sch.h"
@@ -635,35 +636,108 @@ SoftVector *Transceiver2::pullRadioVector(GSM::Time &wTime, int &RSSI,
if (rc < 0)
goto release;
- if(type == TSC){
- unsigned char outbin[148];
+#if 0
+ if(type == SCH){
+ unsigned char outbin[148] = {0};
+
+ bits = demodAnyBurst(*burst, type, rx_sps, &ebp);
+
auto start = reinterpret_cast<float*>(burst->begin());
for(int i=0; i < 625*2; i++)
- start[i] *= 1./32767.;
-
- int ret = process_vita_burst(reinterpret_cast<std::complex<float>*>(burst->begin()), mTSC, outbin);
- bits = new SoftVector();
- bits->resize(148);
- for(int i=0; i < 148; i++)
- (*bits)[i] = outbin[i] < 1 ? -1 : 1;
-
- // printme = ret >= 0 ? true : false;
-
- // if(printme) {
- // std::cerr << std::endl << "vita:" << std::endl;
- // for(auto i : outbin)
- // std::cerr << (int) i;
- // std::cerr << std::endl << "org:" << std::endl;
- // }
- } else {
- /* Ignore noise threshold on MS mode for now */
- //if ((type == SCH) || (avg - state->mNoiseLev > 0.0))
- bits = demodAnyBurst(*burst, type, rx_sps, &ebp);
+ start[i] *= 1./2047.;
+
+ auto ss = reinterpret_cast<std::complex<float>*>(burst->begin());
+
+ // int ret = process_vita_sc_burst(ss, mTSC, outbin, 0);
+
+ {
+ std::vector<gr_complex> channel_imp_resp(CHAN_IMP_RESP_LENGTH* d_OSR);
+ /* Get channel impulse response */
+ auto d_c0_burst_start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0], 0, (SYNC_POS + SYNC_SEARCH_RANGE) * d_OSR + SYNC_POS * d_OSR );
+
+ if(d_c0_burst_start < 0) {
+ std::cerr << " fck! offset <0! " << ebp.toa << std::endl;
+ d_c0_burst_start = 0;
+ } else {
+ std::cerr << " ## offset " << d_c0_burst_start << " vs " << ebp.toa << std::endl;
+ }
+ /* Perform MLSE detection */
+ detect_burst(ss, &channel_imp_resp[0],
+ d_c0_burst_start, outbin);
+ }
+
+ // auto bits2 = new SoftVector();
+ // bits2->resize(156);
+ // for(int i=0; i < 148; i++)
+ // (*bits2)[i] = outbin[i] < 1 ? -1 : 1;
+
- // if(printme)
- // for(int i=0; i < 148; i++)
- // std::cerr << (int) (bits->operator[](i) > 0 ? 1 : 0);
+
+ printme = true;
+
+ if(printme) {
+ std::cerr << std::endl << "vita:" << std::endl;
+ for(auto i : outbin)
+ std::cerr << (int) i;
+ std::cerr << std::endl << "org:" << std::endl;
+ for(int i=0; i < 148; i++)
+ std::cerr << (int) (bits->operator[](i) > 0 ? 1 : 0);
+ }
+
+ } else
+#endif
+ if (type == TSC) {
+ unsigned char outbin[148];
+
+ // bits = demodAnyBurst(*burst, type, rx_sps, &ebp);
+
+ {
+ auto start = reinterpret_cast<float *>(burst->begin());
+ for (int i = 0; i < 625 * 2; i++)
+ start[i] *= 1. / 2047.;
+
+ auto ss = reinterpret_cast<std::complex<float> *>(burst->begin());
+ // int ret = process_vita_burst(reinterpret_cast<std::complex<float>*>(burst->begin()), mTSC, outbin);
+
+ float ncmax, dcmax;
+ std::vector<gr_complex> channel_imp_resp(CHAN_IMP_RESP_LENGTH * d_OSR),
+ channel_imp_resp2(CHAN_IMP_RESP_LENGTH * d_OSR);
+ auto normal_burst_start = get_norm_chan_imp_resp(ss, &channel_imp_resp[0], &ncmax, mTSC);
+ auto dummy_burst_start = get_norm_chan_imp_resp(ss, &channel_imp_resp2[0], &dcmax, TS_DUMMY);
+ auto is_nb = ncmax > dcmax;
+
+ std::cerr << " ## " << is_nb << " o nb " << normal_burst_start << " db " << dummy_burst_start
+ << " vs " << ebp.toa << std::endl;
+
+ if (is_nb)
+ detect_burst(ss, &channel_imp_resp[0], normal_burst_start, outbin);
+ else
+ detect_burst(ss, &channel_imp_resp2[0], dummy_burst_start, outbin);
+ ;
+ }
+
+ bits = new SoftVector();
+ bits->resize(148);
+ for (int i = 0; i < 148; i++)
+ (*bits)[i] = outbin[i] < 1 ? -1 : 1;
+
+ // printme = ret >= 0 ? true : false;
+
+ // if(printme) {
+ // std::cerr << std::endl << "vita:" << std::endl;
+ // for(auto i : outbin)
+ // std::cerr << (int) i;
+ // std::cerr << std::endl << "org:" << std::endl;
+ // }
+ } else {
+ /* Ignore noise threshold on MS mode for now */
+ //if ((type == SCH) || (avg - state->mNoiseLev > 0.0))
+ bits = demodAnyBurst(*burst, type, rx_sps, &ebp);
+
+ // if(printme)
+ // for(int i=0; i < 148; i++)
+ // std::cerr << (int) (bits->operator[](i) > 0 ? 1 : 0);
}
/* MS: Decode SCH and adjust GSM clock */
diff --git a/Transceiver52M/grgsm_vitac/constants.h b/Transceiver52M/grgsm_vitac/constants.h
index f678cd4..e57ef43 100644
--- a/Transceiver52M/grgsm_vitac/constants.h
+++ b/Transceiver52M/grgsm_vitac/constants.h
@@ -28,7 +28,7 @@
#define TS_PER_FRAME 8
#define FRAME_BITS (TS_PER_FRAME * TS_BITS + 2) // 156.25 * 8
#define FCCH_POS TAIL_BITS
-#define SYNC_POS 39
+#define SYNC_POS (TAIL_BITS + 39)
#define TRAIN_POS ( TAIL_BITS + (DATA_BITS+STEALING_BIT) + 5) //first 5 bits of a training sequence
//aren't used for channel impulse response estimation
#define TRAIN_BEGINNING 5
diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
index a31c8bd..b4b6134 100644
--- a/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
+++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp
@@ -20,8 +20,7 @@
* Boston, MA 02110-1301, USA.
*/
-#include "grgsm_vitac/constants.h"
-#define _CRT_SECURE_NO_WARNINGS
+#include "constants.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -37,61 +36,12 @@
#include <fstream>
#include "viterbi_detector.h"
-
-
+#include "grgsm_vitac.h"
//signalVector mChanResp;
gr_complex d_sch_training_seq[N_SYNC_BITS]; ///<encoded training sequence of a SCH burst
gr_complex d_norm_training_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS]; ///<encoded training sequences of a normal and dummy burst
-int get_norm_chan_imp_resp(const gr_complex* input, gr_complex* chan_imp_resp, float* corr_max, int* corr_max_index);
-
-#define SYNC_SEARCH_RANGE 30
-
-const int d_OSR(4);
-const int d_chan_imp_length(CHAN_IMP_RESP_LENGTH);
-std::vector<gr_complex> channel_imp_resp(CHAN_IMP_RESP_LENGTH* d_OSR);
-
-void initv();
-void process();
-int
-get_sch_chan_imp_resp(const gr_complex* input,
- gr_complex* chan_imp_resp);
-void
-detect_burst(const gr_complex* input,
- gr_complex* chan_imp_resp, int burst_start,
- unsigned char* output_binary);
-void
-gmsk_mapper(const unsigned char* input,
- int nitems, gr_complex* gmsk_output, gr_complex start_point)
- ;
-gr_complex
-correlate_sequence(const gr_complex* sequence,
- int length, const gr_complex* input)
- ;
-inline void
-autocorrelation(const gr_complex* input,
- gr_complex* out, int nitems)
- ;
-inline void
-mafi(const gr_complex* input, int nitems,
- gr_complex* filter, int filter_length, gr_complex* output)
- ;
-int
-get_norm_chan_imp_resp(const gr_complex* input,
- gr_complex* chan_imp_resp, float* corr_max, int bcc)
- ;
-
-
-struct fdata {
- unsigned int fn;
- int tn;
- int bcc;
- std::string fpath;
- std::vector<gr_complex> data;
-};
-
-
-std::vector<fdata> files_to_process;
+const int d_chan_imp_length = CHAN_IMP_RESP_LENGTH;
void initvita() {
@@ -104,6 +54,8 @@ void initvita() {
*/
gmsk_mapper(SYNC_BITS, N_SYNC_BITS,
d_sch_training_seq, gr_complex(0.0, -1.0));
+ for (auto &i : d_sch_training_seq)
+ i = conj(i);
/* Prepare bits of training sequences */
for (int i = 0; i < TRAIN_SEQ_NUM; i++) {
@@ -115,92 +67,12 @@ void initvita() {
gr_complex(1.0, 0.0) : gr_complex(-1.0, 0.0);
gmsk_mapper(train_seq[i], N_TRAIN_BITS,
d_norm_training_seq[i], startpoint);
+ for (auto &i : d_norm_training_seq[i])
+ i = conj(i);
}
}
-int
-get_sch_chan_imp_resp(const gr_complex* input,
- gr_complex* chan_imp_resp)
-{
- std::vector<gr_complex> correlation_buffer;
- std::vector<float> window_energy_buffer;
- std::vector<float> power_buffer;
-
- int chan_imp_resp_center = 0;
- int strongest_window_nr;
- int burst_start;
- float energy = 0;
-
- int len = (SYNC_POS + SYNC_SEARCH_RANGE) * d_OSR;
- for (int ii = SYNC_POS * d_OSR; ii < len; ii++) {
- gr_complex correlation = correlate_sequence(&d_sch_training_seq[5],
- N_SYNC_BITS - 10, &input[ii]);
- correlation_buffer.push_back(correlation);
- power_buffer.push_back(std::pow(abs(correlation), 2));
- }
-
- /* Compute window energies */
- std::vector<float>::iterator iter = power_buffer.begin();
- while (iter != power_buffer.end()) {
- std::vector<float>::iterator iter_ii = iter;
- bool loop_end = false;
- energy = 0;
-
- for (int ii = 0; ii < (d_chan_imp_length)*d_OSR; ii++, iter_ii++) {
- if (iter_ii == power_buffer.end()) {
- loop_end = true;
- break;
- }
-
- energy += (*iter_ii);
- }
-
- if (loop_end)
- break;
-
- window_energy_buffer.push_back(energy);
- iter++;
- }
-
- strongest_window_nr = max_element(window_energy_buffer.begin(),
- window_energy_buffer.end()) - window_energy_buffer.begin();
-
-#if 0
- d_channel_imp_resp.clear();
-#endif
-
- float max_correlation = 0;
- for (int ii = 0; ii < (d_chan_imp_length)*d_OSR; ii++) {
- gr_complex correlation = correlation_buffer[strongest_window_nr + ii];
- if (abs(correlation) > max_correlation) {
- chan_imp_resp_center = ii;
- max_correlation = abs(correlation);
- }
-
-#if 0
- d_channel_imp_resp.push_back(correlation);
-#endif
-
- chan_imp_resp[ii] = correlation;
- }
-
- burst_start = strongest_window_nr + chan_imp_resp_center
- - 48 * d_OSR - 2 * d_OSR + 2 + SYNC_POS * d_OSR;
- return burst_start;
-}
-
-
-#if defined(__has_attribute)
- #if __has_attribute(target_clones)
- #if defined(__x86_64)
- #define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx","sse4.2","sse3","sse2","sse","default")))
- #endif
- #else
- #define MULTI_VER_TARGET_ATTR
- #endif
-#endif
-
MULTI_VER_TARGET_ATTR
void
detect_burst(const gr_complex* input,
@@ -228,12 +100,9 @@ detect_burst(const gr_complex* input,
output_binary[i] = output[i] > 0;
}
-
-
-int d_c0_burst_start;
-
int process_vita_burst(gr_complex* input, int tsc, unsigned char* output_binary) {
- unsigned int normal_burst_start, dummy_burst_start;
+ gr_complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ int normal_burst_start, dummy_burst_start;
float dummy_corr_max, normal_corr_max;
dummy_burst_start = get_norm_chan_imp_resp(input,
@@ -242,45 +111,30 @@ int process_vita_burst(gr_complex* input, int tsc, unsigned char* output_binary)
&channel_imp_resp[0], &normal_corr_max, tsc);
if (normal_corr_max > dummy_corr_max) {
- d_c0_burst_start = normal_burst_start;
-
/* Perform MLSE detection */
detect_burst(input, &channel_imp_resp[0],
normal_burst_start, output_binary);
return 0;
- }
- else {
- d_c0_burst_start = dummy_burst_start;
+ } else {
memcpy(output_binary, dummy_burst, 148);
//std::cerr << std::endl << "#NOPE#" << dd.fpath << std::endl << std::endl;
return -1;
}
-
}
int process_vita_sc_burst(gr_complex* input, int tsc, unsigned char* output_binary, int* offset) {
-
- int ncc, bcc;
- int t1, t2, t3;
- int rc;
+ gr_complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
/* Get channel impulse response */
- d_c0_burst_start = get_sch_chan_imp_resp(input,
- &channel_imp_resp[0]);
-
+ int d_c0_burst_start = get_sch_chan_imp_resp(input, &channel_imp_resp[0]);
+ // *offset = d_c0_burst_start;
/* Perform MLSE detection */
detect_burst(input, &channel_imp_resp[0],
d_c0_burst_start, output_binary);
- /**
- * Decoding was successful, now
- * compute offset from burst_start,
- * burst should start after a guard period.
- */
- *offset = d_c0_burst_start - floor((GUARD_PERIOD) * d_OSR);
-
+ return 0;
}
void
@@ -316,9 +170,9 @@ correlate_sequence(const gr_complex* sequence,
gr_complex result(0.0, 0.0);
for (int ii = 0; ii < length; ii++)
- result += sequence[ii] * conj(input[ii * d_OSR]);
+ result += sequence[ii] * input[ii * d_OSR];
- return result / gr_complex(length, 0);
+ return conj(result) / gr_complex(length, 0);
}
/* Computes autocorrelation for positive arguments */
@@ -350,35 +204,27 @@ mafi(const gr_complex* input, int nitems,
}
}
-/* Especially computations of strongest_window_nr */
-int
-get_norm_chan_imp_resp(const gr_complex* input,
- gr_complex* chan_imp_resp, float* corr_max, int bcc)
+int get_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, int search_center, int search_start_pos,
+ int search_stop_pos, gr_complex *tseq, int tseqlen, float *corr_max)
{
std::vector<gr_complex> correlation_buffer;
std::vector<float> window_energy_buffer;
std::vector<float> power_buffer;
- int search_center = (int)(TRAIN_POS + 0) * d_OSR;
- int search_start_pos = search_center + 1 - 5 * d_OSR;
- int search_stop_pos = search_center
- + d_chan_imp_length * d_OSR + 5 * d_OSR;
-
for (int ii = search_start_pos; ii < search_stop_pos; ii++) {
- gr_complex correlation = correlate_sequence(
- &d_norm_training_seq[bcc][TRAIN_BEGINNING],
- N_TRAIN_BITS - 10, &input[ii]);
+ gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[ii]);
correlation_buffer.push_back(correlation);
power_buffer.push_back(std::pow(abs(correlation), 2));
}
-#if 0
- plot(power_buffer);
-#endif
+ int strongest_corr_nr = max_element(power_buffer.begin(), power_buffer.end()) - power_buffer.begin();
/* Compute window energies */
- std::vector<float>::iterator iter = power_buffer.begin();
- while (iter != power_buffer.end()) {
+ auto window_energy_start_offset = strongest_corr_nr - 6 * d_OSR;
+ auto window_energy_end_offset = strongest_corr_nr + 6 * d_OSR + d_chan_imp_length * d_OSR;
+ auto iter = power_buffer.begin() + window_energy_start_offset;
+ auto iter_end = power_buffer.begin() + window_energy_end_offset;
+ while (iter != iter_end) {
std::vector<float>::iterator iter_ii = iter;
bool loop_end = false;
float energy = 0;
@@ -401,23 +247,25 @@ get_norm_chan_imp_resp(const gr_complex* input,
}
/* Calculate the strongest window number */
- int strongest_window_nr = max_element(window_energy_buffer.begin(),
- window_energy_buffer.end() - d_chan_imp_length * d_OSR)
- - window_energy_buffer.begin();
+ int strongest_window_nr = window_energy_start_offset +
+ max_element(window_energy_buffer.begin(), window_energy_buffer.end()) -
+ window_energy_buffer.begin();
+
+ // auto window_search_start = window_energy_buffer.begin() + strongest_corr_nr - 5* d_OSR;
+ // auto window_search_end = window_energy_buffer.begin() + strongest_corr_nr + 10* d_OSR;
+ // window_search_end = window_search_end >= window_energy_buffer.end() ? window_energy_buffer.end() : window_search_end;
+
+ // /* Calculate the strongest window number */
+ // int strongest_window_nr = max_element(window_search_start, window_search_end /* - d_chan_imp_length * d_OSR*/) - window_energy_buffer.begin();
- if (strongest_window_nr < 0)
- strongest_window_nr = 0;
+ // if (strongest_window_nr < 0)
+ // strongest_window_nr = 0;
float max_correlation = 0;
for (int ii = 0; ii < d_chan_imp_length * d_OSR; ii++) {
gr_complex correlation = correlation_buffer[strongest_window_nr + ii];
if (abs(correlation) > max_correlation)
max_correlation = abs(correlation);
-
-#if 0
- d_channel_imp_resp.push_back(correlation);
-#endif
-
chan_imp_resp[ii] = correlation;
}
@@ -427,8 +275,57 @@ get_norm_chan_imp_resp(const gr_complex* input,
* Compute first sample position, which corresponds
* to the first sample of the impulse response
*/
- return search_start_pos + strongest_window_nr - TRAIN_POS * d_OSR;
+ return search_start_pos + strongest_window_nr - search_center * d_OSR;
}
+/*
+
+3 + 57 + 1 + 26 + 1 + 57 + 3 + 8.25
+
+search center = 3 + 57 + 1 + 5 (due to tsc 5+16+5 split)
+this is +-5 samples around (+5 beginning) of truncated t16 tsc
+
+*/
+int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc)
+{
+ const int search_center = TRAIN_POS;
+ const int search_start_pos = (search_center - 5) * d_OSR + 1;
+ const int search_stop_pos = (search_center + 5 + d_chan_imp_length) * d_OSR;
+ const auto tseq = &d_norm_training_seq[bcc][TRAIN_BEGINNING];
+ const auto tseqlen = N_TRAIN_BITS - (2 * TRAIN_BEGINNING);
+ return get_chan_imp_resp(input, chan_imp_resp, search_center, search_start_pos, search_stop_pos, tseq, tseqlen,
+ corr_max);
+}
+/*
+
+3 tail | 39 data | 64 tsc | 39 data | 3 tail | 8.25 guard
+start 3+39 - 10
+end 3+39 + SYNC_SEARCH_RANGE
+
+*/
+int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp)
+{
+ const int search_center = SYNC_POS + TRAIN_BEGINNING;
+ const int search_start_pos = (search_center - 10) * d_OSR;
+ const int search_stop_pos = (search_center + SYNC_SEARCH_RANGE) * d_OSR;
+ const auto tseq = &d_sch_training_seq[TRAIN_BEGINNING];
+ const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING);
+
+ // strongest_window_nr + chan_imp_resp_center + SYNC_POS *d_OSR - 48 * d_OSR - 2 * d_OSR + 2 ;
+ float corr_max;
+ return get_chan_imp_resp(input, chan_imp_resp, search_center, search_start_pos, search_stop_pos, tseq, tseqlen,
+ &corr_max);
+}
+int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max)
+{
+ const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING);
+ const int search_center = SYNC_POS + TRAIN_BEGINNING;
+ const int search_start_pos = 0;
+ const int search_stop_pos = len - tseqlen;
+ auto tseq = &d_sch_training_seq[TRAIN_BEGINNING];
+
+ return get_chan_imp_resp(input, chan_imp_resp, search_center, search_start_pos, search_stop_pos, tseq, tseqlen,
+ corr_max);
+} \ No newline at end of file
diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.h b/Transceiver52M/grgsm_vitac/grgsm_vitac.h
new file mode 100644
index 0000000..3c8e2b8
--- /dev/null
+++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.h
@@ -0,0 +1,42 @@
+
+#pragma once
+
+#include <vector>
+#include "constants.h"
+
+#if defined(__has_attribute)
+#if __has_attribute(target_clones) && defined(__x86_64) && false
+#define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default")))
+#else
+#define MULTI_VER_TARGET_ATTR
+#endif
+#endif
+
+#define SYNC_SEARCH_RANGE 30
+const int d_OSR(4);
+
+void initvita();
+
+int process_vita_burst(gr_complex *input, int tsc, unsigned char *output_binary);
+int process_vita_sc_burst(gr_complex *input, int tsc, unsigned char *output_binary, int *offset);
+
+MULTI_VER_TARGET_ATTR
+void detect_burst(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, unsigned char *output_binary);
+void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point);
+gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input);
+inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems);
+inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output);
+int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp);
+int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc);
+int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max);
+
+enum class btype { NB, SCH };
+struct fdata {
+ btype t;
+ unsigned int fn;
+ int tn;
+ int bcc;
+ std::string fpath;
+ std::vector<gr_complex> data;
+ unsigned int data_start_offset;
+}; \ No newline at end of file
diff --git a/Transceiver52M/itrq.h b/Transceiver52M/itrq.h
new file mode 100644
index 0000000..f086af3
--- /dev/null
+++ b/Transceiver52M/itrq.h
@@ -0,0 +1,204 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <atomic>
+
+#include <condition_variable>
+#include <mutex>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+/*
+classic lamport circular lockfree spsc queue:
+every "side" only writes its own ptr, but may read the other sides ptr
+
+notify reader using eventfd as soon as element is added, reader then reads until
+read fails
+-> reader pops in a loop until FALSE and might get spurious events because it
+read before it was notified, which is fine
+-> writing pushes *the same data* in a loop until TRUE, blocks
+
+shutting this down requires
+1) to stop reading and pushing
+2) ONE side to take care of the eventfds
+*/
+
+namespace spsc_detail
+{
+template <bool block_read, bool block_write> class spsc_cond_detail {
+ std::condition_variable cond_r, cond_w;
+ std::mutex l;
+
+ public:
+ explicit spsc_cond_detail()
+ {
+ }
+
+ ~spsc_cond_detail()
+ {
+ }
+
+ ssize_t spsc_check_r()
+ {
+ std::unique_lock<std::mutex> lk(l);
+ cond_r.wait(lk);
+ return 1;
+ }
+ ssize_t spsc_check_w()
+ {
+ std::unique_lock<std::mutex> lk(l);
+ cond_w.wait(lk);
+ return 1;
+ }
+ void spsc_notify_r()
+ {
+ cond_r.notify_one();
+ }
+ void spsc_notify_w()
+ {
+ cond_w.notify_one();
+ }
+};
+
+// originally designed for select loop integration
+template <bool block_read, bool block_write> class spsc_efd_detail {
+ int efd_r, efd_w; /* eventfds used to block/notify readers/writers */
+
+ public:
+ explicit spsc_efd_detail()
+ : efd_r(eventfd(0, block_read ? 0 : EFD_NONBLOCK)), efd_w(eventfd(1, block_write ? 0 : EFD_NONBLOCK))
+ {
+ }
+
+ ~spsc_efd_detail()
+ {
+ close(efd_r);
+ close(efd_w);
+ }
+
+ ssize_t spsc_check_r()
+ {
+ uint64_t efdr;
+ return read(efd_r, &efdr, sizeof(uint64_t));
+ }
+ ssize_t spsc_check_w()
+ {
+ uint64_t efdr;
+ return read(efd_w, &efdr, sizeof(uint64_t));
+ }
+ void spsc_notify_r()
+ {
+ uint64_t efdu = 1;
+ write(efd_r, &efdu, sizeof(uint64_t));
+ }
+ void spsc_notify_w()
+ {
+ uint64_t efdu = 1;
+ write(efd_w, &efdu, sizeof(uint64_t));
+ }
+ int get_r_efd()
+ {
+ return efd_r;
+ }
+ int get_w_efd()
+ {
+ return efd_w;
+ }
+};
+
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write, template <bool, bool> class T>
+class spsc : public T<block_read, block_write> {
+ static_assert(SZ > 0, "queues need a size...");
+ std::atomic<unsigned int> readptr;
+ std::atomic<unsigned int> writeptr;
+
+ ELEM buf[SZ];
+
+ public:
+ using base_t = T<block_read, block_write>;
+ using elem_t = ELEM;
+ explicit spsc() : readptr(0), writeptr(0)
+ {
+ }
+
+ ~spsc()
+ {
+ }
+
+ /*! Adds element to the queue by copying the data.
+ * \param[in] elem input buffer, must match the originally configured queue buffer size!.
+ * \returns true if queue was not full and element was successfully pushed */
+ bool spsc_push(ELEM *elem)
+ {
+ size_t cur_wp, cur_rp;
+ cur_wp = writeptr.load(std::memory_order_relaxed);
+ cur_rp = readptr.load(std::memory_order_acquire);
+ if ((cur_wp + 1) % SZ == cur_rp) {
+ if (block_write)
+ base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */
+ return false;
+ }
+ buf[cur_wp] = *elem;
+ writeptr.store((cur_wp + 1) % SZ, std::memory_order_release);
+ if (block_read)
+ base_t::spsc_notify_r(); /* fine after release */
+ return true;
+ }
+
+ /*! Removes element from the queue by copying the data.
+ * \param[in] elem output buffer, must match the originally configured queue buffer size!.
+ * \returns true if queue was not empty and element was successfully removed */
+ bool spsc_pop(ELEM *elem)
+ {
+ size_t cur_wp, cur_rp;
+ cur_wp = writeptr.load(std::memory_order_acquire);
+ cur_rp = readptr.load(std::memory_order_relaxed);
+
+ if (cur_wp == cur_rp) /* blocks via prep_pop */
+ return false;
+
+ *elem = buf[cur_rp];
+ readptr.store((cur_rp + 1) % SZ, std::memory_order_release);
+ if (block_write)
+ base_t::spsc_notify_w();
+ return true;
+ }
+
+ /*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks.
+ * This function can be used to deliberately wait for a non-empty queue on the read side.
+ * \returns result of reading the fd. */
+ ssize_t spsc_prep_pop()
+ {
+ return base_t::spsc_check_r();
+ }
+};
+
+} // namespace spsc_detail
+
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {};
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {}; \ No newline at end of file
diff --git a/Transceiver52M/l1if.cpp b/Transceiver52M/l1if.cpp
index 440f6e7..385395a 100644
--- a/Transceiver52M/l1if.cpp
+++ b/Transceiver52M/l1if.cpp
@@ -84,7 +84,7 @@ void push_c(TRX_C* i) {
uint64_t one = 1;
int rc;
trxif.c_from_trx.push(i);
- std::clog << trxif.c_from_trx.sz() << std::endl;
+ // std::clog << trxif.c_from_trx.sz() << std::endl;
rc = ::write(trxif.g_event_ofd_C.fd, &one, sizeof(one));
return;
};
diff --git a/Transceiver52M/l1if.h b/Transceiver52M/l1if.h
index d477e47..ab6ccce 100644
--- a/Transceiver52M/l1if.h
+++ b/Transceiver52M/l1if.h
@@ -29,20 +29,20 @@ extern "C" {
/* 148 bytes output symbol values, 0 & 1 */
/* ------------------------------------------------------------------------ */
-struct trxd_to_trx {
- uint8_t ts;
- uint32_t fn;
- uint8_t txlev;
- uint8_t symbols[148];
+struct __attribute__((packed)) trxd_to_trx {
+ uint8_t ts;
+ uint32_t fn;
+ uint8_t txlev;
+ uint8_t symbols[148];
};
-struct trxd_from_trx {
- uint8_t ts;
- uint32_t fn;
- uint8_t rssi;
- uint16_t toa;
- uint8_t symbols[148];
- uint8_t pad[2];
+struct __attribute__((packed)) trxd_from_trx {
+ uint8_t ts;
+ uint32_t fn;
+ uint8_t rssi;
+ uint16_t toa;
+ uint8_t symbols[148];
+ uint8_t pad[2];
};
#define TRXC_BUF_SIZE 1024
diff --git a/Transceiver52M/ms/bladerf_specific.h b/Transceiver52M/ms/bladerf_specific.h
new file mode 100644
index 0000000..fcfcc9c
--- /dev/null
+++ b/Transceiver52M/ms/bladerf_specific.h
@@ -0,0 +1,482 @@
+#pragma once
+
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "itrq.h"
+#include <atomic>
+#include <complex>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <cassert>
+#include <cstring>
+
+#include <libbladeRF.h>
+#include <Timeval.h>
+#include <unistd.h>
+
+const size_t BLADE_BUFFER_SIZE = 1024 * 1;
+const size_t BLADE_NUM_BUFFERS = 32 * 1;
+const size_t NUM_TRANSFERS = 16 * 2;
+const int SAMPLE_SCALE_FACTOR = 15; // actually 16 but sigproc complains about clipping..
+
+template <typename Arg, typename... Args> void doPrint(std::ostream &out, Arg &&arg, Args &&...args)
+{
+ out << '(' << std::forward<Arg>(arg);
+ using expander = int[];
+ (void)expander{ 0, (void(out << ',' << std::forward<Args>(args)), 0)... };
+ out << ')' << std::endl;
+}
+
+template <class R, class... Args> using RvalFunc = R (*)(Args...);
+
+// specialisation for funcs which return a value
+template <class R, class... Args>
+R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finame, const char *funcname, int line,
+ Args... args)
+{
+ R rval = func(std::forward<Args>(args)...);
+ if (rval != 0) {
+ std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':'
+ << funcname << ':' << fname;
+ doPrint(std::cerr, args...);
+ }
+ return rval;
+}
+
+// only macros can pass a func name string
+#define blade_check(func, ...) exec_and_check(func, #func, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
+
+#pragma pack(push, 1)
+using blade_sample_type = std::complex<int16_t>;
+enum class blade_speed_buffer_type { HS, SS };
+template <blade_speed_buffer_type T> struct blade_usb_message {
+ uint32_t reserved;
+ uint64_t ts;
+ uint32_t meta_flags;
+ blade_sample_type d[(T == blade_speed_buffer_type::SS ? 512 : 256) - 4];
+};
+
+static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::SS>) == 2048, "blade buffer mismatch!");
+static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::HS>) == 1024, "blade buffer mismatch!");
+template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer {
+ static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!");
+ blade_usb_message<T> m[SZ];
+ int actual_samples_per_msg()
+ {
+ return sizeof(blade_usb_message<T>::d) / sizeof(typeof(blade_usb_message<T>::d[0]));
+ }
+ int actual_samples_per_buffer()
+ {
+ return SZ * actual_samples_per_msg();
+ }
+ int samples_per_buffer()
+ {
+ return SZ * sizeof(blade_usb_message<T>) / sizeof(typeof(blade_usb_message<T>::d[0]));
+ }
+ int num_msgs_per_buffer()
+ {
+ return SZ;
+ }
+ auto get_first_ts()
+ {
+ return m[0].ts;
+ }
+ constexpr auto *getsampleoffset(int ofs)
+ {
+ auto full = ofs / actual_samples_per_msg();
+ auto rem = ofs % actual_samples_per_msg();
+ return &m[full].d[rem];
+ }
+ int readall(blade_sample_type *outaddr)
+ {
+ blade_sample_type *addr = outaddr;
+ for (int i = 0; i < SZ; i++) {
+ memcpy(addr, &m[i].d[0], actual_samples_per_msg() * sizeof(blade_sample_type));
+ addr += actual_samples_per_msg();
+ }
+ return actual_samples_per_buffer();
+ }
+ int read_n(blade_sample_type *outaddr, int start, int num)
+ {
+ assert((start + num) <= actual_samples_per_buffer());
+ assert(start >= 0);
+
+ if (!num)
+ return 0;
+
+ // which buffer?
+ int start_buf_idx = (start > 0) ? start / actual_samples_per_msg() : 0;
+ // offset from actual buffer start
+ auto start_offset_in_buf = (start - (start_buf_idx * actual_samples_per_msg()));
+ auto samp_rem_in_first_buf = actual_samples_per_msg() - start_offset_in_buf;
+ auto remaining_first_buf = num > samp_rem_in_first_buf ? samp_rem_in_first_buf : num;
+
+ memcpy(outaddr, &m[start_buf_idx].d[start_offset_in_buf],
+ remaining_first_buf * sizeof(blade_sample_type));
+ outaddr += remaining_first_buf;
+
+ auto remaining = num - remaining_first_buf;
+
+ if (!remaining)
+ return num;
+
+ start_buf_idx++;
+
+ auto rem_full_bufs = remaining / actual_samples_per_msg();
+ remaining -= rem_full_bufs * actual_samples_per_msg();
+
+ for (int i = 0; i < rem_full_bufs; i++) {
+ memcpy(outaddr, &m[start_buf_idx++].d[0], actual_samples_per_msg() * sizeof(blade_sample_type));
+ outaddr += actual_samples_per_msg();
+ }
+
+ if (remaining)
+ memcpy(outaddr, &m[start_buf_idx].d[0], remaining * sizeof(blade_sample_type));
+ return num;
+ }
+ int write_n_burst(blade_sample_type *in, int num, uint64_t first_ts)
+ {
+ assert(num <= actual_samples_per_buffer());
+ int len_rem = num;
+ for (int i = 0; i < SZ; i++) {
+ m[i] = {};
+ m[i].ts = first_ts + i * actual_samples_per_msg();
+ if (len_rem) {
+ int max_to_copy =
+ len_rem > actual_samples_per_msg() ? actual_samples_per_msg() : len_rem;
+ memcpy(&m[i].d[0], in, max_to_copy * sizeof(blade_sample_type));
+ len_rem -= max_to_copy;
+ in += actual_samples_per_msg();
+ }
+ }
+ return num;
+ }
+};
+#pragma pack(pop)
+
+template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer_helper {
+ static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!");
+ static blade_otw_buffer<SZ / 512, T> x;
+};
+
+using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_buffer_type::SS>::x);
+// using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>;
+using bh_fn_t = std::function<int(dev_buf_t *)>;
+
+template <typename T> struct blade_hw {
+ struct bladerf *dev;
+ struct bladerf_stream *rx_stream;
+ struct bladerf_stream *tx_stream;
+ // using pkt2buf = blade_otw_buffer<2, blade_speed_buffer_type::SS>;
+ using tx_buf_q_type = spsc_cond<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>;
+ unsigned int rxFullScale, txFullScale;
+ int rxtxdelay;
+
+ float rxgain, txgain;
+
+ struct ms_trx_config {
+ int tx_freq;
+ int rx_freq;
+ int sample_rate;
+ int bandwidth;
+
+ public:
+ ms_trx_config() : tx_freq(881e6), rx_freq(926e6), sample_rate(((1625e3 / 6) * 4)), bandwidth(1e6)
+ {
+ }
+ } cfg;
+
+ struct buf_mgmt {
+ void **rx_samples;
+ void **tx_samples;
+ tx_buf_q_type bufptrqueue;
+
+ } buf_mgmt;
+
+ virtual ~blade_hw()
+ {
+ close_device();
+ }
+ blade_hw() : rxFullScale(2047), txFullScale(2047), rxtxdelay(-60)
+ {
+ }
+
+ void close_device()
+ {
+ if (dev) {
+ if (rx_stream) {
+ bladerf_deinit_stream(rx_stream);
+ }
+
+ if (tx_stream) {
+ bladerf_deinit_stream(tx_stream);
+ }
+
+ bladerf_enable_module(dev, BLADERF_MODULE_RX, false);
+ bladerf_enable_module(dev, BLADERF_MODULE_TX, false);
+
+ bladerf_close(dev);
+ dev = NULL;
+ }
+ }
+
+ int init_device(bh_fn_t rxh, bh_fn_t txh)
+ {
+ struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)) * 64, 6 * 64 }, actual;
+
+ bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_DEBUG);
+ bladerf_set_usb_reset_on_open(true);
+ blade_check(bladerf_open, &dev, "");
+ if (!dev) {
+ std::cerr << "open failed, device missing?" << std::endl;
+ return -1;
+ }
+ if (bladerf_device_speed(dev) != bladerf_dev_speed::BLADERF_DEVICE_SPEED_SUPER) {
+ std::cerr << "open failed, only superspeed (usb3) supported!" << std::endl;
+ return -1;
+ }
+
+ blade_check(bladerf_set_tuning_mode, dev, bladerf_tuning_mode::BLADERF_TUNING_MODE_FPGA);
+
+ bool is_locked;
+ blade_check(bladerf_set_pll_enable, dev, true);
+ blade_check(bladerf_set_pll_refclk, dev, 10000000UL);
+ for (int i = 0; i < 20; i++) {
+ usleep(50 * 1000);
+ bladerf_get_pll_lock_state(dev, &is_locked);
+
+ if (is_locked)
+ break;
+ }
+ if (!is_locked) {
+ std::cerr << "unable to lock refclk!" << std::endl;
+ return -1;
+ }
+
+ // bladerf_sample_rate r = (1625e3 * 4)/6, act;
+ // blade_check(bladerf_set_sample_rate,dev, BLADERF_CHANNEL_RX(0), r, &act);
+ // blade_check(bladerf_set_sample_rate,dev, BLADERF_CHANNEL_TX(0), r, &act);
+
+ // auto ratrate = (1625e3 * 4) / 6;
+ // rate.integer = (uint32_t)ratrate;
+ // rate.den = 10000;
+ // rate.num = (ratrate - rate.integer) * rate.den;
+
+ blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
+ blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
+
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)cfg.rx_freq);
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)cfg.tx_freq);
+
+ blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)cfg.bandwidth,
+ (bladerf_bandwidth *)NULL);
+ blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)cfg.bandwidth,
+ (bladerf_bandwidth *)NULL);
+
+ blade_check(bladerf_set_gain_mode, dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC);
+ // blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)30);
+ // blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)50);
+ usleep(1000);
+ blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true);
+ usleep(1000);
+ blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true);
+ usleep(1000);
+ blade_check(bladerf_init_stream, &rx_stream, dev, getrxcb(rxh), &buf_mgmt.rx_samples, BLADE_NUM_BUFFERS,
+ BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
+
+ blade_check(bladerf_init_stream, &tx_stream, dev, gettxcb(txh), &buf_mgmt.tx_samples, BLADE_NUM_BUFFERS,
+ BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
+
+ for (int i = 0; i < BLADE_NUM_BUFFERS; i++) {
+ auto cur_buffer = reinterpret_cast<tx_buf_q_type::elem_t *>(buf_mgmt.tx_samples);
+ buf_mgmt.bufptrqueue.spsc_push(&cur_buffer[i]);
+ }
+
+ setRxGain(20);
+ setTxGain(30);
+
+ usleep(1000);
+
+ // bladerf_set_stream_timeout(dev, BLADERF_TX, 4);
+ // bladerf_set_stream_timeout(dev, BLADERF_RX, 4);
+
+ return 0;
+ }
+
+ bool tuneTx(double freq, size_t chan = 0)
+ {
+ msleep(15);
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq);
+ msleep(15);
+ return true;
+ };
+ bool tuneRx(double freq, size_t chan = 0)
+ {
+ msleep(15);
+ blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq);
+ msleep(15);
+ return true;
+ };
+ bool tuneRxOffset(double offset, size_t chan = 0)
+ {
+ return true;
+ };
+
+ double setRxGain(double dB, size_t chan = 0)
+ {
+ rxgain = dB;
+ msleep(15);
+ blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)dB);
+ msleep(15);
+ return dB;
+ };
+ double setTxGain(double dB, size_t chan = 0)
+ {
+ txgain = dB;
+ msleep(15);
+ blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)dB);
+ msleep(15);
+ return dB;
+ };
+ int setPowerAttenuation(int atten, size_t chan = 0)
+ {
+ return atten;
+ };
+
+ static void check_timestamp(dev_buf_t *rcd)
+ {
+ static bool first = true;
+ static uint64_t last_ts;
+ if (first) {
+ first = false;
+ last_ts = rcd->m[0].ts;
+ } else if (last_ts + rcd->actual_samples_per_buffer() != rcd->m[0].ts) {
+ std::cerr << "RX Overrun!" << last_ts << " " << rcd->actual_samples_per_buffer() << " "
+ << last_ts + rcd->actual_samples_per_buffer() << " " << rcd->m[0].ts << std::endl;
+ last_ts = rcd->m[0].ts;
+ } else {
+ last_ts = rcd->m[0].ts;
+ }
+ }
+
+ bladerf_stream_cb getrxcb(bh_fn_t rxbh)
+ {
+ // C cb -> no capture!
+ static auto rxbhfn = rxbh;
+ return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta,
+ void *samples, size_t num_samples, void *user_data) -> void * {
+ // struct blade_hw *trx = (struct blade_hw *)user_data;
+ static int to_skip = 0;
+ dev_buf_t *rcd = (dev_buf_t *)samples;
+
+ if (to_skip < 120) // prevents weird overflows on startup
+ to_skip++;
+ else {
+ check_timestamp(rcd);
+ rxbhfn(rcd);
+ }
+
+ return samples;
+ };
+ }
+ bladerf_stream_cb gettxcb(bh_fn_t txbh)
+ {
+ // C cb -> no capture!
+ static auto txbhfn = txbh;
+ return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta,
+ void *samples, size_t num_samples, void *user_data) -> void * {
+ struct blade_hw *trx = (struct blade_hw *)user_data;
+ auto ptr = reinterpret_cast<tx_buf_q_type::elem_t>(samples);
+
+ if (samples) // put buffer address back into queue, ready to be reused
+ trx->buf_mgmt.bufptrqueue.spsc_push(&ptr);
+
+ return BLADERF_STREAM_NO_DATA;
+ };
+ }
+
+ auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [this] {
+ int status;
+ set_name_aff_sched("rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2);
+ status = bladerf_stream(rx_stream, BLADERF_RX_X1);
+ if (status < 0)
+ std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
+
+ return NULL;
+ };
+ return fn;
+ }
+ auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [this] {
+ int status;
+ set_name_aff_sched("txrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
+ status = bladerf_stream(tx_stream, BLADERF_TX_X1);
+ if (status < 0)
+ std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
+
+ return NULL;
+ };
+ return fn;
+ }
+
+ void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
+ {
+ //get empty bufer from list
+ tx_buf_q_type::elem_t rcd;
+
+ while (!buf_mgmt.bufptrqueue.spsc_pop(&rcd))
+ buf_mgmt.bufptrqueue.spsc_prep_pop();
+ assert(rcd != nullptr);
+
+ rcd->write_n_burst(buffer, len, ts + rxtxdelay); // blade xa4 specific delay!
+ // blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd, 100U);
+ blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd);
+ }
+
+ void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
+ {
+ pthread_setname_np(pthread_self(), name);
+
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpunum, &cpuset);
+
+ auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
+ if (rv < 0) {
+ std::cerr << name << " affinity: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+
+ sched_param sch_params;
+ sch_params.sched_priority = prio;
+ rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
+ if (rv < 0) {
+ std::cerr << name << " sched: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+ }
+};
diff --git a/Transceiver52M/ms/ms_commandhandler.cpp b/Transceiver52M/ms/ms_commandhandler.cpp
new file mode 100644
index 0000000..13b95f9
--- /dev/null
+++ b/Transceiver52M/ms/ms_commandhandler.cpp
@@ -0,0 +1,229 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <radioInterface.h>
+#include "l1if.h"
+#include "ms_rx_upper.h"
+#include "syncthing.h"
+#include "ms_state.h"
+
+void upper_trx::driveControl()
+{
+#ifdef IPCIF
+ auto m = pop_c();
+ if (!m)
+ return;
+#else
+ TRX_C cmd;
+
+ socklen_t addr_len = sizeof(ctrlsrc);
+ int rdln = recvfrom(mCtrlSockets, (void *)cmd.cmd, sizeof(cmd) - 1, 0, &ctrlsrc, &addr_len);
+ if (rdln < 0 && errno == EAGAIN) {
+ std::cerr << "fuck, send ctrl?" << std::endl;
+ exit(0);
+ }
+
+ TRX_C *m = &cmd;
+#endif
+
+ auto response = (TRX_C *)malloc(sizeof(TRX_C));
+ response->cmd[0] = '\0';
+ commandhandler(m->cmd, response->cmd);
+#ifdef IPCIF
+ free(m);
+#endif
+ std::clog << "response is " << response->cmd << std::endl;
+#ifdef IPCIF
+ push_c(response);
+#else
+
+ int rv = sendto(mCtrlSockets, response, strlen(response->cmd) + 1, 0, &ctrlsrc, sizeof(struct sockaddr_in));
+ if (rv < 0) {
+ std::cerr << "fuck, rcv ctrl?" << std::endl;
+ exit(0);
+ }
+ free(response);
+
+#endif
+}
+
+void upper_trx::commandhandler(char *buffer, char *response)
+{
+ int MAX_PACKET_LENGTH = TRXC_BUF_SIZE;
+
+ char cmdcheck[4];
+ char command[MAX_PACKET_LENGTH];
+
+ sscanf(buffer, "%3s %s", cmdcheck, command);
+
+ if (strcmp(cmdcheck, "CMD") != 0) {
+ LOG(WARNING) << "bogus message on control interface";
+ return;
+ }
+ std::clog << "command is " << buffer << std::endl << std::flush;
+
+ if (strcmp(command, "MEASURE") == 0) {
+ msleep(100);
+ int freq;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &freq);
+ sprintf(response, "RSP MEASURE 0 %d -80", freq);
+ } else if (strcmp(command, "ECHO") == 0) {
+ msleep(100);
+ sprintf(response, "RSP ECHO 0");
+ } else if (strcmp(command, "POWEROFF") == 0) {
+ set_ta(0);
+ // turn off transmitter/demod
+ sprintf(response, "RSP POWEROFF 0");
+ } else if (strcmp(command, "POWERON") == 0) {
+ // turn on transmitter/demod
+ if (!mTxFreq || !mRxFreq)
+ sprintf(response, "RSP POWERON 1");
+ else {
+ sprintf(response, "RSP POWERON 0");
+ if (!mOn) {
+ // Prepare for thread start
+ mPower = -20;
+ start_ms();
+
+ writeClockInterface();
+ mOn = true;
+ }
+ }
+ } else if (strcmp(command, "SETMAXDLY") == 0) {
+ //set expected maximum time-of-arrival
+ int maxDelay;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &maxDelay);
+ mMaxExpectedDelay = maxDelay; // 1 GSM symbol is approx. 1 km
+ sprintf(response, "RSP SETMAXDLY 0 %d", maxDelay);
+ } else if (strcmp(command, "SETRXGAIN") == 0) {
+ //set expected maximum time-of-arrival
+ int newGain;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &newGain);
+ newGain = setRxGain(newGain);
+ sprintf(response, "RSP SETRXGAIN 0 %d", newGain);
+ } else if (strcmp(command, "NOISELEV") == 0) {
+ if (mOn) {
+ float lev = 0; //mStates[chan].mNoiseLev;
+ sprintf(response, "RSP NOISELEV 0 %d", (int)round(20.0 * log10(rxFullScale / lev)));
+ } else {
+ sprintf(response, "RSP NOISELEV 1 0");
+ }
+ } else if (!strcmp(command, "SETPOWER")) {
+ // set output power in dB
+ int dbPwr;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbPwr);
+ if (!mOn)
+ sprintf(response, "RSP SETPOWER 1 %d", dbPwr);
+ else {
+ mPower = dbPwr;
+ setPowerAttenuation(mPower);
+ sprintf(response, "RSP SETPOWER 0 %d", dbPwr);
+ }
+ } else if (!strcmp(command, "ADJPOWER")) {
+ // adjust power in dB steps
+ int dbStep;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbStep);
+ if (!mOn)
+ sprintf(response, "RSP ADJPOWER 1 %d", mPower);
+ else {
+ mPower += dbStep;
+ setPowerAttenuation(mPower);
+ sprintf(response, "RSP ADJPOWER 0 %d", mPower);
+ }
+ } else if (strcmp(command, "RXTUNE") == 0) {
+ // tune receiver
+ int freqKhz;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqKhz);
+ mRxFreq = freqKhz * 1e3;
+ if (!tuneRx(mRxFreq)) {
+ LOG(ALERT) << "RX failed to tune";
+ sprintf(response, "RSP RXTUNE 1 %d", freqKhz);
+ } else
+ sprintf(response, "RSP RXTUNE 0 %d", freqKhz);
+ } else if (strcmp(command, "TXTUNE") == 0) {
+ // tune txmtr
+ int freqKhz;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqKhz);
+ mTxFreq = freqKhz * 1e3;
+ if (!tuneTx(mTxFreq)) {
+ LOG(ALERT) << "TX failed to tune";
+ sprintf(response, "RSP TXTUNE 1 %d", freqKhz);
+ } else
+ sprintf(response, "RSP TXTUNE 0 %d", freqKhz);
+ } else if (!strcmp(command, "SETTSC")) {
+ // set TSC
+ unsigned TSC;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC);
+ if (mOn)
+ sprintf(response, "RSP SETTSC 1 %d", TSC);
+ // else if (chan && (TSC != mTSC))
+ // sprintf(response, "RSP SETTSC 1 %d", TSC);
+ else {
+ mTSC = TSC;
+ //generateMidamble(rx_sps, TSC);
+ sprintf(response, "RSP SETTSC 0 %d", TSC);
+ }
+ } else if (!strcmp(command, "GETBSIC")) {
+ if (mBSIC < 0)
+ sprintf(response, "RSP GETBSIC 1");
+ else
+ sprintf(response, "RSP GETBSIC 0 %d", mBSIC);
+ } else if (strcmp(command, "SETSLOT") == 0) {
+ // set TSC
+ int corrCode;
+ int timeslot;
+ sscanf(buffer, "%3s %s %d %d", cmdcheck, command, &timeslot, &corrCode);
+ if ((timeslot < 0) || (timeslot > 7)) {
+ LOG(WARNING) << "bogus message on control interface";
+ sprintf(response, "RSP SETSLOT 1 %d %d", timeslot, corrCode);
+ return;
+ }
+ mStates.chanType[timeslot] = (ChannelCombination)corrCode;
+ mStates.setModulus(timeslot);
+ sprintf(response, "RSP SETSLOT 0 %d %d", timeslot, corrCode);
+ } else if (!strcmp(command, "SETRXMASK")) {
+ int slot;
+ unsigned long long mask;
+ sscanf(buffer, "%3s %s %d 0x%llx", cmdcheck, command, &slot, &mask);
+ if ((slot < 0) || (slot > 7)) {
+ sprintf(response, "RSP SETRXMASK 1");
+ } else {
+ mRxSlotMask[slot] = mask;
+ sprintf(response, "RSP SETRXMASK 0 %d 0x%llx", slot, mask);
+ }
+ } else if (!strcmp(command, "SYNC")) {
+ // msleep(10);
+ mStates.mode = trx_mode::TRX_MODE_MS_TRACK;
+ sprintf(response, "RSP SYNC 0");
+ mMaxExpectedDelay = 48;
+ // setRxGain(30);
+ // msleep(10);
+ } else if (!strcmp(command, "SETTA")) {
+ int ta;
+ sscanf(buffer, "%3s %s %d", cmdcheck, command, &ta);
+ set_ta(ta);
+ sprintf(response, "RSP SETTA 0 %d", ta);
+ } else {
+ LOG(WARNING) << "bogus command " << command << " on control interface.";
+ }
+
+ //mCtrlSockets[chan]->write(response, strlen(response) + 1);
+}
diff --git a/Transceiver52M/ms/ms_rx_burst.cpp b/Transceiver52M/ms/ms_rx_burst.cpp
new file mode 100644
index 0000000..999584d
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_burst.cpp
@@ -0,0 +1,211 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "syncthing.h"
+#include "sigProcLib.h"
+#include "signalVector.h"
+#include "grgsm_vitac/grgsm_vitac.h"
+extern "C" {
+#include "sch.h"
+}
+
+#if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG)
+#define DBGLG(...) ms_trx::dummy_log()
+#else
+#define DBGLG(...) std::cerr
+#endif
+
+#if !defined(SYNCTHINGONLY)
+#define DBGLG2(...) ms_trx::dummy_log()
+#else
+#define DBGLG2(...) std::cerr
+#endif
+
+__attribute__((xray_always_instrument)) __attribute__((noinline)) static bool decode_sch(float *bits,
+ bool update_global_clock)
+{
+ struct sch_info sch;
+ ubit_t info[GSM_SCH_INFO_LEN];
+ sbit_t data[GSM_SCH_CODED_LEN];
+
+ float_to_sbit(&bits[3], &data[0], 62, 39);
+ float_to_sbit(&bits[106], &data[39], 62, 39);
+
+ if (!gsm_sch_decode(info, data)) {
+ gsm_sch_parse(info, &sch);
+
+ DBGLG() << "SCH : Decoded values" << std::endl;
+ DBGLG() << " BSIC: " << sch.bsic << std::endl;
+ DBGLG() << " TSC: " << (sch.bsic & 0x7) << std::endl;
+ DBGLG() << " T1 : " << sch.t1 << std::endl;
+ DBGLG() << " T2 : " << sch.t2 << std::endl;
+ DBGLG() << " T3p : " << sch.t3p << std::endl;
+ DBGLG() << " FN : " << gsm_sch_to_fn(&sch) << std::endl;
+ return true;
+ }
+ return false;
+}
+
+static void check_rcv_fn(GSM::Time t, bool first, unsigned int &lastfn, unsigned int &fnbm)
+{
+ if (first && t.TN() == 0) {
+ lastfn = t.FN();
+ fnbm = 1 << 0;
+ first = false;
+ }
+ if (!first && t.FN() != lastfn) {
+ if (fnbm != 255)
+ std::cerr << "rx " << lastfn << ":" << fnbm << " " << __builtin_popcount(fnbm) << std::endl;
+ lastfn = t.FN();
+ fnbm = 1 << t.TN();
+ }
+
+ fnbm |= 1 << t.TN();
+}
+
+__attribute__((xray_always_instrument)) __attribute__((noinline)) static void
+handle_it(one_burst &e, signalVector &burst, unsigned int tsc)
+{
+ memset(burst.begin(), 0, burst.size() * sizeof(std::complex<float>));
+ auto is_sch = gsm_sch_check_fn(e.gsmts.FN()) && e.gsmts.TN() == 0;
+ auto is_fcch = gsm_fcch_check_fn(e.gsmts.FN()) && e.gsmts.TN() == 0;
+
+ // if (is_sch)
+ // return;
+
+ if (is_fcch)
+ return;
+
+ if (is_sch) {
+ unsigned char outbin[148];
+ convert_and_scale_default<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2);
+ std::stringstream dbgout;
+#if 0
+ {
+ struct estim_burst_params ebp;
+ auto rv2 = detectSCHBurst(burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
+ auto bits = demodAnyBurst(burst, SCH, 4, &ebp);
+ // clamp_array(bits->begin(), 148, 1.5f);
+ for (auto &i : *bits)
+ i = (i > 0 ? 1 : -1);
+
+ auto rv = decode_sch(bits->begin(), false);
+ dbgout << "U DET@" << (rv2 ? "yes " : " ") << "Timing offset " << ebp.toa
+ << " symbols, DECODE: " << (rv ? "yes" : "---") << " ";
+
+ delete bits;
+ }
+#endif
+ {
+ convert_and_scale<float, float>(burst.begin(), burst.begin(), ONE_TS_BURST_LEN * 2,
+ 1.f / 32767.f);
+
+ std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
+ int d_c0_burst_start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]);
+ detect_burst(ss, &channel_imp_resp[0], d_c0_burst_start, outbin);
+
+ SoftVector bits;
+ bits.resize(148);
+ for (int i = 0; i < 148; i++) {
+ bits[i] = (!outbin[i]) < 1 ? -1 : 1;
+ }
+
+ auto rv = decode_sch(bits.begin(), false);
+ dbgout << "U SCH@"
+ << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << d_c0_burst_start
+ << " DECODE:" << (rv ? "yes" : "---") << std::endl;
+ }
+
+ DBGLG() << dbgout.str();
+ return;
+ }
+#if 1
+ convert_and_scale<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2, 1.f / 2047.f);
+ // std::cerr << "@" << tsc << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << " "
+ // << std::endl;
+
+ unsigned char outbin[148];
+ auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
+ float ncmax, dcmax;
+ std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR], chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
+ auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, tsc);
+ auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
+ auto is_nb = ncmax > dcmax;
+
+ DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start << " o db: " << dummy_burst_start
+ << std::endl;
+
+ if (is_nb)
+ detect_burst(ss, &chan_imp_resp[0], normal_burst_start, outbin);
+ else
+ detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
+ ;
+
+ auto bits = SoftVector(148);
+ for (int i = 0; i < 148; i++)
+ (bits)[i] = outbin[i] < 1 ? -1 : 1;
+
+#endif
+}
+
+__attribute__((xray_always_instrument)) __attribute__((noinline)) void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc)
+{
+ static bool first = true;
+ unsigned int lastfn = 0;
+ unsigned int fnbm = 0;
+ signalVector burst(ONE_TS_BURST_LEN, 100, 100);
+
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(1, &cpuset);
+
+ auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
+ if (rv < 0) {
+ std::cerr << "affinity: errreur! " << std::strerror(errno);
+ exit(0);
+ }
+
+ int prio = sched_get_priority_max(SCHED_RR);
+ struct sched_param param;
+ param.sched_priority = prio;
+ rv = sched_setscheduler(0, SCHED_RR, &param);
+ if (rv < 0) {
+ std::cerr << "scheduler: errreur! " << std::strerror(errno);
+ exit(0);
+ }
+
+ while (1) {
+ one_burst e;
+ while (!q->spsc_pop(&e)) {
+ q->spsc_prep_pop();
+ }
+
+ check_rcv_fn(e.gsmts, first, lastfn, fnbm);
+
+ handle_it(e, burst, *tsc);
+
+ // rv = detectSCHBurst(*burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
+ // if (rv > 0)
+ // std::cerr << "#" << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << std::endl;
+ // sched_yield();
+ }
+} \ No newline at end of file
diff --git a/Transceiver52M/ms/ms_rx_burst.h b/Transceiver52M/ms/ms_rx_burst.h
new file mode 100644
index 0000000..301ebcf
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_burst.h
@@ -0,0 +1,25 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "syncthing.h"
+
+void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc); \ No newline at end of file
diff --git a/Transceiver52M/ms/ms_rx_lower.cpp b/Transceiver52M/ms/ms_rx_lower.cpp
new file mode 100644
index 0000000..15e562c
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_lower.cpp
@@ -0,0 +1,330 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sigProcLib.h"
+#include "signalVector.h"
+#include <atomic>
+#include <cassert>
+#include <complex>
+#include <iostream>
+#include <future>
+
+#include "syncthing.h"
+#include "grgsm_vitac/grgsm_vitac.h"
+
+extern "C" {
+#include "sch.h"
+}
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#if !defined(SYNCTHINGONLY) //|| !defined(NODAMNLOG)
+#define DBGLG(...) ms_trx::dummy_log()
+#else
+#define DBGLG(...) std::cerr
+#endif
+
+#if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG)
+#define DBGLG2(...) ms_trx::dummy_log()
+#else
+#define DBGLG2(...) std::cerr
+#endif
+
+#define PRINT_Q_OVERFLOW
+__attribute__((xray_always_instrument)) __attribute__((noinline)) bool ms_trx::decode_sch(float *bits,
+ bool update_global_clock)
+{
+ int fn;
+ struct sch_info sch;
+ ubit_t info[GSM_SCH_INFO_LEN];
+ sbit_t data[GSM_SCH_CODED_LEN];
+
+ float_to_sbit(&bits[3], &data[0], 62, 39);
+ float_to_sbit(&bits[106], &data[39], 62, 39);
+
+ if (!gsm_sch_decode(info, data)) {
+ gsm_sch_parse(info, &sch);
+
+ if (update_global_clock) {
+ DBGLG() << "SCH : Decoded values" << std::endl;
+ DBGLG() << " BSIC: " << sch.bsic << std::endl;
+ DBGLG() << " TSC: " << (sch.bsic & 0x7) << std::endl;
+ DBGLG() << " T1 : " << sch.t1 << std::endl;
+ DBGLG() << " T2 : " << sch.t2 << std::endl;
+ DBGLG() << " T3p : " << sch.t3p << std::endl;
+ DBGLG() << " FN : " << gsm_sch_to_fn(&sch) << std::endl;
+ }
+
+ fn = gsm_sch_to_fn(&sch);
+ if (fn < 0) { // how? wh?
+ DBGLG() << "SCH : Failed to convert FN " << std::endl;
+ return false;
+ }
+
+ if (update_global_clock) {
+ mBSIC = sch.bsic;
+ mTSC = sch.bsic & 0x7;
+ timekeeper.set(fn, 0);
+ // global_time_keeper.FN(fn);
+ // global_time_keeper.TN(0);
+ }
+#ifdef SYNCTHINGONLY
+ else {
+ int t3 = sch.t3p * 10 + 1;
+ if (t3 == 11) {
+ // timeslot hitter attempt @ fn 21 in mf
+ DBGLG2() << "sch @ " << t3 << std::endl;
+ auto e = GSM::Time(fn, 0);
+ e += 10;
+ ts_hitter_q.spsc_push(&e);
+ }
+ }
+#endif
+ // auto sch11 = gsm_sch_check_fn(fn + 11);
+ // DBGLG() << "next sch: "<< (sch11 ? "11":"10")<<" first ts " << first_sch_buf_rcv_ts << std::endl;
+ return true;
+ }
+ return false;
+}
+
+void ms_trx::maybe_update_gain(one_burst &brst)
+{
+ static_assert((sizeof(brst.burst) / sizeof(brst.burst[0])) == ONE_TS_BURST_LEN, "wtf, buffer size mismatch?");
+ const int avgburst_num = 8 * 20; // ~ 50*4.5ms = 90ms?
+ static_assert(avgburst_num * 577 > (50 * 1000), "can't update faster then blade wait time?");
+ const unsigned int rx_max_cutoff = (rxFullScale * 2) / 3;
+ static int gain_check = 0;
+ static float runmean = 0;
+ float sum = 0;
+ for (auto i : brst.burst)
+ sum += abs(i.real()) + abs(i.imag());
+ sum /= ONE_TS_BURST_LEN * 2;
+
+ runmean = gain_check ? (runmean * (gain_check + 2) - 1 + sum) / (gain_check + 2) : sum;
+
+ if (gain_check == avgburst_num - 1) {
+ DBGLG2() << "\x1B[32m #RXG \033[0m" << rxgain << " " << runmean << " " << sum << std::endl;
+ auto gainoffset = runmean < (rxFullScale / 4 ? 4 : 2);
+ gainoffset = runmean < (rxFullScale / 2 ? 2 : 1);
+ float newgain = runmean < rx_max_cutoff ? rxgain + gainoffset : rxgain - gainoffset;
+ // FIXME: gian cutoff
+ if (newgain != rxgain && newgain <= 60)
+ std::thread([this, newgain] { setRxGain(newgain); }).detach();
+ runmean = 0;
+ }
+ gain_check = (gain_check + 1) % avgburst_num;
+}
+
+bool ms_trx::handle_sch_or_nb(bool get_first_sch)
+{
+ one_burst brst;
+ auto current_gsm_time = timekeeper.gsmtime();
+ brst.gsmts = current_gsm_time;
+ memcpy(brst.burst, burst_copy_buffer, sizeof(blade_sample_type) * ONE_TS_BURST_LEN);
+ auto pushok = rxqueue.spsc_push(&brst);
+#ifdef PRINT_Q_OVERFLOW
+ if (!pushok)
+ std::cout << "F" << std::endl;
+#endif
+ if (do_auto_gain)
+ maybe_update_gain(brst);
+
+ // only continue for SCH, don't touch FCCH
+ auto is_sch = gsm_sch_check_fn(current_gsm_time.FN()) && current_gsm_time.TN() == 0;
+ auto is_fcch = gsm_fcch_check_fn(current_gsm_time.FN()) && current_gsm_time.TN() == 0;
+#pragma unused(is_fcch)
+
+ if (!is_sch) {
+ // sched_yield();
+ return false;
+ }
+ auto rv = handle_sch(false);
+ // sched_yield();
+ return rv;
+}
+
+float bernd[SCH_LEN_SPS * 2];
+
+bool ms_trx::handle_sch(bool is_first_sch_acq)
+{
+ struct estim_burst_params ebp;
+ auto current_gsm_time = timekeeper.gsmtime();
+ const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN;
+ const auto which_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer;
+
+ std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ unsigned char outbin[148];
+ float max_corr = 0;
+ const auto ss = reinterpret_cast<std::complex<float> *>(&bernd[0]);
+ convert_and_scale<float, int16_t>(bernd, which_buffer, buf_len * 2, 1.f / 2047.f);
+
+ auto start = is_first_sch_acq ? get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr) :
+ get_sch_chan_imp_resp(ss, channel_imp_resp);
+ detect_burst(&ss[start], &channel_imp_resp[0], 0, outbin);
+
+ SoftVector bitss(148);
+ for (int i = 0; i < 148; i++) {
+ bitss[i] = (!outbin[i]) < 1 ? -1 : 1;
+ }
+
+ auto sch_decode_success = decode_sch(bitss.begin(), is_first_sch_acq);
+
+ if (sch_decode_success) {
+ const auto ts_offset_symb = 0;
+ if (is_first_sch_acq) {
+ // update ts to first sample in sch buffer, to allow delay calc for current ts
+ first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4);
+ } else if (abs(start) > 1) {
+ // continuous sch tracking, only update if off too much
+ temp_ts_corr_offset += -start;
+ std::cerr << "offs: " << start << " " << temp_ts_corr_offset << std::endl;
+ }
+
+ return true;
+ } else {
+ DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN()
+ << ":" << current_gsm_time.TN() << std::endl;
+ }
+ return false;
+}
+
+__attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd)
+{
+ static unsigned int sch_pos = 0;
+ if (sch_thread_done)
+ return SCH_STATE::FOUND;
+
+ if (rcv_done)
+ return SCH_STATE::SEARCHING;
+
+ auto to_copy = SCH_LEN_SPS - sch_pos;
+
+ if (SCH_LEN_SPS == to_copy) // first time
+ first_sch_buf_rcv_ts = rcd->get_first_ts();
+
+ if (!to_copy) {
+ sch_pos = 0;
+ rcv_done = true;
+ std::thread([this] {
+ set_name_aff_sched("sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5);
+
+ auto ptr = reinterpret_cast<const int16_t *>(first_sch_buf);
+ const auto target_val = rxFullScale / 8;
+ float sum = 0;
+ for (int i = 0; i < SCH_LEN_SPS * 2; i++)
+ sum += std::abs(ptr[i]);
+ sum /= SCH_LEN_SPS * 2;
+
+ //FIXME: arbitrary value, gain cutoff
+ if (sum > target_val || rxgain >= 60) // enough ?
+ sch_thread_done = this->handle_sch(true);
+ else {
+ std::cerr << "\x1B[32m #RXG \033[0m gain " << rxgain << " -> " << rxgain + 4
+ << " sample avg:" << sum << " target: >=" << target_val << std::endl;
+ setRxGain(rxgain + 4);
+ }
+
+ if (!sch_thread_done)
+ rcv_done = false; // retry!
+ return (bool)sch_thread_done;
+ }).detach();
+ }
+
+ auto spsmax = rcd->actual_samples_per_buffer();
+ if (to_copy > spsmax)
+ sch_pos += rcd->readall(first_sch_buf + sch_pos);
+ else
+ sch_pos += rcd->read_n(first_sch_buf + sch_pos, 0, to_copy);
+
+ return SCH_STATE::SEARCHING;
+}
+__attribute__((optnone)) void ms_trx::grab_bursts(dev_buf_t *rcd)
+{
+ // partial burst samples read from the last buffer
+ static int partial_rdofs = 0;
+ static bool first_call = true;
+ int to_skip = 0;
+
+ // round up to next burst by calculating the time between sch detection and now
+ if (first_call) {
+ const auto next_burst_start = rcd->get_first_ts() - first_sch_ts_start;
+ const auto fullts = next_burst_start / ONE_TS_BURST_LEN;
+ const auto fracts = next_burst_start % ONE_TS_BURST_LEN;
+ to_skip = ONE_TS_BURST_LEN - fracts;
+
+ for (int i = 0; i < fullts; i++)
+ timekeeper.inc_and_update(first_sch_ts_start + i * ONE_TS_BURST_LEN);
+
+ if (fracts)
+ timekeeper.inc_both();
+ // timekeeper.inc_and_update(first_sch_ts_start + 1 * ONE_TS_BURST_LEN);
+
+ timekeeper.dec_by_one(); // oops, off by one?
+
+ timekeeper.set(timekeeper.gsmtime(), rcd->get_first_ts() - ONE_TS_BURST_LEN + to_skip);
+
+ DBGLG() << "this ts: " << rcd->get_first_ts() << " diff full TN: " << fullts << " frac TN: " << fracts
+ << " GSM now: " << timekeeper.gsmtime().FN() << ":" << timekeeper.gsmtime().TN() << " is sch? "
+ << gsm_sch_check_fn(timekeeper.gsmtime().FN()) << std::endl;
+ first_call = false;
+ }
+
+ if (partial_rdofs) {
+ auto first_remaining = ONE_TS_BURST_LEN - partial_rdofs;
+ // memcpy(burst_copy_buffer, partial_buf, partial_rdofs * sizeof(blade_sample_type));
+ auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining);
+ if (rd != first_remaining) {
+ partial_rdofs += rd;
+ return;
+ }
+
+ timekeeper.inc_and_update_safe(rcd->get_first_ts() - partial_rdofs);
+ handle_sch_or_nb();
+ to_skip = first_remaining;
+ }
+
+ // apply sample rate slippage compensation
+ to_skip -= temp_ts_corr_offset;
+
+ // FIXME: happens rarely, read_n start -1 blows up
+ // this is fine: will just be corrected one buffer later
+ if (to_skip < 0)
+ to_skip = 0;
+ else
+ temp_ts_corr_offset = 0;
+
+ const auto left_after_burst = rcd->actual_samples_per_buffer() - to_skip;
+
+ const int full = left_after_burst / ONE_TS_BURST_LEN;
+ const int frac = left_after_burst % ONE_TS_BURST_LEN;
+
+ for (int i = 0; i < full; i++) {
+ rcd->read_n(burst_copy_buffer, to_skip + i * ONE_TS_BURST_LEN, ONE_TS_BURST_LEN);
+ timekeeper.inc_and_update_safe(rcd->get_first_ts() + to_skip + i * ONE_TS_BURST_LEN);
+ handle_sch_or_nb();
+ }
+
+ if (frac)
+ rcd->read_n(burst_copy_buffer, to_skip + full * ONE_TS_BURST_LEN, frac);
+ partial_rdofs = frac;
+}
diff --git a/Transceiver52M/ms/ms_rx_upper.cpp b/Transceiver52M/ms/ms_rx_upper.cpp
new file mode 100644
index 0000000..b87986e
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_upper.cpp
@@ -0,0 +1,364 @@
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sigProcLib.h"
+#include "syncthing.h"
+#include "l1if.h"
+#include <signalVector.h>
+#include <radioVector.h>
+#include <radioInterface.h>
+#include "grgsm_vitac/grgsm_vitac.h"
+#include "ms_state.h"
+#include "ms_rx_upper.h"
+
+extern "C" {
+#include "sch.h"
+#include "convolve.h"
+#include "convert.h"
+#include "proto_trxd.h"
+}
+
+#ifdef LOG
+#undef LOG
+#define LOG(...) upper_trx::dummy_log()
+#endif
+
+void upper_trx::start_threads()
+{
+ thr_rx = std::thread([this] {
+ set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5);
+ while (1) {
+ driveReceiveFIFO();
+ pthread_testcancel();
+ }
+ });
+ msleep(1);
+
+ thr_control = std::thread([this] {
+ set_name_aff_sched("upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR));
+ while (1) {
+ driveControl();
+ pthread_testcancel();
+ }
+ });
+ msleep(1);
+ thr_tx = std::thread([this] {
+ set_name_aff_sched("upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
+
+ while (1) {
+ driveTx();
+ pthread_testcancel();
+ }
+ });
+}
+
+void upper_trx::start_ms()
+{
+ ms_trx::start();
+}
+
+/* Detect SCH synchronization sequence within a burst */
+bool upper_trx::detectSCH(ms_TransceiverState *state, signalVector &burst, struct estim_burst_params *ebp)
+{
+ int shift;
+ sch_detect_type full;
+ float mag, threshold = 4.0;
+
+ full = (state->mode == trx_mode::TRX_MODE_MS_TRACK) ? sch_detect_type::SCH_DETECT_NARROW :
+ sch_detect_type::SCH_DETECT_FULL;
+
+ if (!detectSCHBurst(burst, threshold, rx_sps, full, ebp))
+ return false;
+
+ std::clog << "SCH : Timing offset " << ebp->toa << " symbols" << std::endl;
+
+ mag = fabsf(ebp->toa);
+ if (mag < 1.0f)
+ return true;
+
+ shift = (int)(mag / 2.0f);
+ if (!shift)
+ shift++;
+
+ shift = ebp->toa > 0 ? shift : -shift;
+ std::clog << "SCH : shift -> " << shift << " symbols" << std::endl;
+ // mRadioInterface->applyOffset(shift);
+ return false;
+}
+
+SoftVector *upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) __attribute__((optnone))
+{
+ float pow, avg = 1.0;
+ signalVector *burst;
+ SoftVector *bits = new SoftVector(148);
+ GSM::Time burst_time;
+
+ one_burst e;
+ unsigned char outbin[148];
+ std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ std::stringstream dbgout;
+
+ while (!rxqueue.spsc_pop(&e)) {
+ rxqueue.spsc_prep_pop();
+ }
+
+ auto sv = signalVector(625, 40);
+ burst = &sv;
+ auto ss = reinterpret_cast<std::complex<float> *>(burst->begin());
+
+ convert_and_scale<float, int16_t>(burst->begin(), e.burst, ONE_TS_BURST_LEN * 2, 1.f / 2047.f);
+
+ /* Set time and determine correlation type */
+ burst_time = e.gsmts;
+
+ wTime = burst_time;
+
+ CorrType type = mStates.expectedCorrType(burst_time, mRxSlotMask);
+
+ switch (mStates.mode) {
+ case trx_mode::TRX_MODE_MS_TRACK:
+ if (gsm_sch_check_fn(burst_time.FN()) && burst_time.TN() == 0)
+ type = SCH;
+ else if (burst_time.TN() == 0 && !gsm_fcch_check_fn(burst_time.FN())) // all ts0, but not fcch or sch..
+ type = TSC;
+ else if (type == OFF)
+ goto release;
+ break;
+
+ case trx_mode::TRX_MODE_OFF:
+ default:
+ goto release;
+ }
+
+ pow = energyDetect(*burst, 20 * rx_sps);
+ if (pow < -1) {
+ LOG(ALERT) << "Received empty burst";
+ goto release;
+ }
+
+ avg = sqrt(pow);
+
+ if (type == SCH) {
+ int d_c0_burst_start = get_sch_chan_imp_resp(ss, &chan_imp_resp[0]);
+ detect_burst(ss, &chan_imp_resp[0], d_c0_burst_start, outbin);
+
+ for (int i = 0; i < 148; i++)
+ (*bits)[i] = (!outbin[i]) < 1 ? -1 : 1;
+
+ // auto rv = decode_sch(bits->begin(), false);
+ // dbgout << "U SCH@"
+ // << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << d_c0_burst_start
+ // << " DECODE:" << (rv ? "yes" : "---") << std::endl;
+
+ // std::cerr << dbgout.str();
+ } else {
+ float ncmax, dcmax;
+ std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
+ auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
+ auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
+ auto is_nb = ncmax > dcmax;
+
+ // std::cerr << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start
+ // << " o db: " << dummy_burst_start << std::endl;
+
+ if (is_nb)
+ detect_burst(ss, &chan_imp_resp[0], normal_burst_start, outbin);
+ else
+ detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
+
+ for (int i = 0; i < 148; i++)
+ (*bits)[i] = (outbin[i]) < 1 ? -1 : 1;
+ }
+
+ RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
+ timingOffset = (int)round(0);
+
+ return bits;
+
+release:
+
+ delete bits;
+ return NULL;
+}
+
+void upper_trx::driveReceiveFIFO()
+{
+ SoftVector *rxBurst = NULL;
+ int RSSI;
+ int TOA; // in 1/256 of a symbol
+ GSM::Time burstTime;
+
+ rxBurst = pullRadioVector(burstTime, RSSI, TOA);
+ if (!mOn)
+ return;
+
+ // _only_ return useless fcch trash to tickle trxcons tx path
+ // auto is_fcch = [&burstTime]{ return burstTime.TN() == 0 && gsm_fcch_check_fn(burstTime.FN());};
+ // if(!rxBurst && !is_fcch())
+ // return;
+
+ auto response = (trxd_from_trx *)calloc(1, sizeof(trxd_from_trx));
+
+ response->ts = burstTime.TN();
+ response->fn = htonl(burstTime.FN());
+ response->rssi = RSSI;
+ response->toa = htons(TOA);
+ if (rxBurst) {
+ SoftVector::const_iterator burstItr = rxBurst->begin();
+ if (gsm_sch_check_fn(burstTime.FN())) {
+ clamp_array(rxBurst->begin(), 148, 1.5f);
+ for (unsigned int i = 0; i < gSlotLen; i++) {
+ auto val = *burstItr++;
+ auto vval = isnan(val) ? 0 : val;
+ ((int8_t *)response->symbols)[i] = round((vval - 0.5) * 64.0);
+ }
+ } else {
+ // invert and fix to +-127 sbits
+ for (int i = 0; i < 148; i++)
+ ((int8_t *)response->symbols)[i] = *burstItr++ > 0.0f ? -127 : 127;
+ }
+
+ delete rxBurst;
+ }
+
+#ifdef IPCIF
+ push_d(response);
+#else
+ int rv = sendto(mDataSockets, response, sizeof(trxd_from_trx), 0, (struct sockaddr *)&datadest,
+ sizeof(struct sockaddr_in));
+ if (rv < 0) {
+ std::cerr << "fuck, send?" << std::endl;
+ exit(0);
+ }
+ free(response);
+
+#endif
+}
+
+void upper_trx::driveTx()
+{
+#ifdef IPCIF
+ auto burst = pop_d();
+ if (!burst) {
+ // std::cerr << "wtf no tx burst?" << std::endl;
+ // exit(0);
+ continue;
+ }
+#else
+ trxd_to_trx buffer;
+
+ socklen_t addr_len = sizeof(datasrc);
+ int rdln = recvfrom(mDataSockets, (void *)&buffer, sizeof(trxd_to_trx), 0, &datasrc, &addr_len);
+ if (rdln < 0 && errno == EAGAIN) {
+ std::cerr << "fuck, rcv?" << std::endl;
+ exit(0);
+ }
+
+ trxd_to_trx *burst = &buffer;
+#endif
+ auto proper_fn = ntohl(burst->fn);
+ // std::cerr << "got burst!" << proper_fn << ":" << burst->ts
+ // << " current: " << timekeeper.gsmtime().FN()
+ // << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)proper_fn)
+ // << std::endl;
+
+ auto currTime = GSM::Time(proper_fn, burst->ts);
+ int RSSI = (int)burst->txlev;
+
+ static BitVector newBurst(gSlotLen);
+ BitVector::iterator itr = newBurst.begin();
+ auto *bufferItr = burst->symbols;
+ while (itr < newBurst.end())
+ *itr++ = *bufferItr++;
+
+ auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
+ scaleVector(*txburst, txFullScale * 0.7 /* * pow(10, -RSSI / 10)*/);
+
+ // float -> int16
+ blade_sample_type burst_buf[txburst->size()];
+ convert_and_scale<int16_t, float>(burst_buf, txburst->begin(), txburst->size() * 2, 1);
+
+ // auto check = signalVector(txburst->size(), 40);
+ // convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2);
+ // estim_burst_params ebp;
+ // auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp);
+ // if(d)
+ // std::cerr << "RACH D! " << ebp.toa << std::endl;
+ // else
+ // std::cerr << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
+
+ // memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
+
+ submit_burst(burst_buf, txburst->size(), currTime);
+
+#ifdef IPCIF
+ free(burst);
+#endif
+}
+
+// __attribute__((xray_always_instrument)) static void *rx_stream_callback(struct bladerf *dev,
+// struct bladerf_stream *stream,
+// struct bladerf_metadata *meta, void *samples,
+// size_t num_samples, void *user_data)
+// {
+// struct ms_trx *trx = (struct ms_trx *)user_data;
+// return trx->rx_cb(dev, stream, meta, samples, num_samples, user_data);
+// }
+
+// __attribute__((xray_always_instrument)) static void *tx_stream_callback(struct bladerf *dev,
+// struct bladerf_stream *stream,
+// struct bladerf_metadata *meta, void *samples,
+// size_t num_samples, void *user_data)
+// {
+// struct ms_trx *trx = (struct ms_trx *)user_data;
+// return BLADERF_STREAM_NO_DATA;
+// }
+
+int trxc_main(int argc, char *argv[])
+{
+ pthread_setname_np(pthread_self(), "main_trxc");
+
+ convolve_init();
+ convert_init();
+ sigProcLibSetup();
+ initvita();
+
+ int status = 0;
+ auto trx = new upper_trx();
+ trx->do_auto_gain = true;
+
+ status = trx->init_dev_and_streams(0, 0);
+ trx->start_threads();
+
+ return status;
+}
+
+extern "C" volatile bool gshutdown = false;
+extern "C" void init_external_transceiver(int argc, char **argv)
+{
+ std::cout << "init?" << std::endl;
+ trxc_main(argc, argv);
+}
+
+extern "C" void stop_trx()
+{
+ std::cout << "Shutting down transceiver..." << std::endl;
+}
diff --git a/Transceiver52M/ms/ms_rx_upper.h b/Transceiver52M/ms/ms_rx_upper.h
new file mode 100644
index 0000000..09154bf
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_upper.h
@@ -0,0 +1,121 @@
+
+#pragma once
+
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <netdb.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "GSMCommon.h"
+#include "radioClock.h"
+#include "syncthing.h"
+#include "ms_state.h"
+
+class upper_trx : public ms_trx {
+ int rx_sps, tx_sps;
+
+ ms_TransceiverState mStates;
+
+ bool mOn; ///< flag to indicate that transceiver is powered on
+ double mTxFreq; ///< the transmit frequency
+ double mRxFreq; ///< the receive frequency
+ int mPower; ///< the transmit power in dB
+ unsigned mMaxExpectedDelay; ///< maximum TOA offset in GSM symbols
+ unsigned long long mRxSlotMask[8]; ///< MS - enabled multiframe slot mask
+
+ int mDataSockets;
+ sockaddr_in datadest;
+ sockaddr datasrc;
+ int mCtrlSockets;
+ sockaddr_in ctrldest;
+ sockaddr ctrlsrc;
+
+ void openudp(int *mSocketFD, unsigned short localPort, const char *wlocalIP)
+ {
+ *mSocketFD = socket(AF_INET, SOCK_DGRAM, 0);
+ int on = 1;
+ setsockopt(*mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ struct sockaddr_in address;
+ size_t length = sizeof(address);
+ bzero(&address, length);
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = inet_addr(wlocalIP);
+ address.sin_port = htons(localPort);
+ if (bind(*mSocketFD, (struct sockaddr *)&address, length) < 0) {
+ std::cerr << "bind fail!" << std::endl;
+ exit(0);
+ }
+ }
+
+ bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port)
+ {
+ struct hostent *hp;
+ int h_errno_local;
+
+ struct hostent hostData;
+ char tmpBuffer[2048];
+
+ auto rc = gethostbyname2_r(host, AF_INET, &hostData, tmpBuffer, sizeof(tmpBuffer), &hp, &h_errno_local);
+ if (hp == NULL || hp->h_addrtype != AF_INET || rc != 0) {
+ std::cerr << "WARNING -- gethostbyname() failed for " << host << ", "
+ << hstrerror(h_errno_local);
+ exit(0);
+ return false;
+ }
+
+ address->sin_family = hp->h_addrtype;
+ assert(sizeof(address->sin_addr) == hp->h_length);
+ memcpy(&(address->sin_addr), hp->h_addr_list[0], hp->h_length);
+ address->sin_port = htons(port);
+ return true;
+ }
+
+ void driveControl();
+ void driveReceiveFIFO();
+ void driveTx();
+ void commandhandler(char *buffer, char *response);
+ void writeClockInterface(){};
+
+ SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset);
+
+ bool detectSCH(ms_TransceiverState *state, signalVector &burst, struct estim_burst_params *ebp);
+
+ std::thread thr_control, thr_rx, thr_tx;
+
+ public:
+ void start_threads();
+ void start_ms();
+
+ upper_trx() : rx_sps(4), tx_sps(4)
+ {
+ auto c_srcport = 6700 + 2 * 0 + 1;
+ auto c_dstport = 6700 + 2 * 0 + 101;
+ auto d_srcport = 6700 + 2 * 0 + 2;
+ auto d_dstport = 6700 + 2 * 0 + 102;
+
+ openudp(&mCtrlSockets, c_srcport, "127.0.0.1");
+ openudp(&mDataSockets, d_srcport, "127.0.0.1");
+ resolveAddress(&ctrldest, "127.0.0.1", c_dstport);
+ resolveAddress(&datadest, "127.0.0.1", d_dstport);
+ };
+};
diff --git a/Transceiver52M/ms/ms_state.h b/Transceiver52M/ms/ms_state.h
new file mode 100644
index 0000000..c00d072
--- /dev/null
+++ b/Transceiver52M/ms/ms_state.h
@@ -0,0 +1,175 @@
+#pragma once
+
+#include <radioVector.h>
+#include <signalVector.h>
+
+enum class trx_mode {
+ TRX_MODE_OFF,
+ TRX_MODE_BTS,
+ TRX_MODE_MS_ACQUIRE,
+ TRX_MODE_MS_TRACK,
+};
+
+enum class ChannelCombination {
+ FILL, ///< Channel is transmitted, but unused
+ I, ///< TCH/FS
+ II, ///< TCH/HS, idle every other slot
+ III, ///< TCH/HS
+ IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH
+ V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4
+ VI, ///< CCCH+BCCH, uplink RACH
+ VII, ///< SDCCH/8 + SACCH/8
+ VIII, ///< TCH/F + FACCH/F + SACCH/M
+ IX, ///< TCH/F + SACCH/M
+ X, ///< TCH/FD + SACCH/MD
+ XI, ///< PBCCH+PCCCH+PDTCH+PACCH+PTCCH
+ XII, ///< PCCCH+PDTCH+PACCH+PTCCH
+ XIII, ///< PDTCH+PACCH+PTCCH
+ NONE_INACTIVE, ///< Channel is inactive, default
+ LOOPBACK ///< similar go VII, used in loopback testing
+};
+
+struct ms_TransceiverState {
+ ms_TransceiverState() : mFreqOffsets(10), mode(trx_mode::TRX_MODE_OFF)
+ {
+ for (int i = 0; i < 8; i++) {
+ chanType[i] = ChannelCombination::NONE_INACTIVE;
+ fillerModulus[i] = 26;
+
+ for (int n = 0; n < 102; n++)
+ fillerTable[n][i] = nullptr;
+ }
+ }
+
+ ~ms_TransceiverState()
+ {
+ for (int i = 0; i < 8; i++) {
+ for (int n = 0; n < 102; n++)
+ delete fillerTable[n][i];
+ }
+ }
+
+ void setModulus(size_t timeslot)
+ {
+ switch (chanType[timeslot]) {
+ case ChannelCombination::NONE_INACTIVE:
+ case ChannelCombination::I:
+ case ChannelCombination::II:
+ case ChannelCombination::III:
+ case ChannelCombination::FILL:
+ fillerModulus[timeslot] = 26;
+ break;
+ case ChannelCombination::IV:
+ case ChannelCombination::VI:
+ case ChannelCombination::V:
+ fillerModulus[timeslot] = 51;
+ break;
+ //case V:
+ case ChannelCombination::VII:
+ fillerModulus[timeslot] = 102;
+ break;
+ case ChannelCombination::XIII:
+ fillerModulus[timeslot] = 52;
+ break;
+ default:
+ break;
+ }
+ }
+
+ CorrType expectedCorrType(GSM::Time currTime, unsigned long long *mRxSlotMask)
+ {
+ unsigned burstTN = currTime.TN();
+ unsigned burstFN = currTime.FN();
+
+ if (mode == trx_mode::TRX_MODE_MS_TRACK) {
+ /* 102 modulus case currently unhandled */
+ if (fillerModulus[burstTN] > 52)
+ return OFF;
+
+ int modFN = burstFN % fillerModulus[burstTN];
+ unsigned long long reg = (unsigned long long)1 << modFN;
+ if (reg & mRxSlotMask[burstTN])
+ return TSC;
+ else
+ return OFF;
+ }
+
+ switch (chanType[burstTN]) {
+ case ChannelCombination::NONE_INACTIVE:
+ return OFF;
+ break;
+ case ChannelCombination::FILL:
+ return IDLE;
+ break;
+ case ChannelCombination::I:
+ return TSC;
+ /*if (burstFN % 26 == 25)
+ return IDLE;
+ else
+ return TSC;*/
+ break;
+ case ChannelCombination::II:
+ return TSC;
+ break;
+ case ChannelCombination::III:
+ return TSC;
+ break;
+ case ChannelCombination::IV:
+ case ChannelCombination::VI:
+ return RACH;
+ break;
+ case ChannelCombination::V: {
+ int mod51 = burstFN % 51;
+ if ((mod51 <= 36) && (mod51 >= 14))
+ return RACH;
+ else if ((mod51 == 4) || (mod51 == 5))
+ return RACH;
+ else if ((mod51 == 45) || (mod51 == 46))
+ return RACH;
+ else
+ return TSC;
+ break;
+ }
+ case ChannelCombination::VII:
+ if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ case ChannelCombination::XIII: {
+ int mod52 = burstFN % 52;
+ if ((mod52 == 12) || (mod52 == 38))
+ return RACH;
+ else if ((mod52 == 25) || (mod52 == 51))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ }
+ case ChannelCombination::LOOPBACK:
+ if ((burstFN % 51 <= 50) && (burstFN % 51 >= 48))
+ return IDLE;
+ else
+ return TSC;
+ break;
+ default:
+ return OFF;
+ break;
+ }
+ }
+
+ /* Initialize a multiframe slot in the filler table */
+ void init(size_t slot, signalVector *burst, bool fill);
+
+ ChannelCombination chanType[8];
+
+ /* The filler table */
+ signalVector *fillerTable[102][8];
+ int fillerModulus[8];
+
+ /* Received noise energy levels */
+ avgVector mFreqOffsets;
+
+ /* Transceiver mode */
+ trx_mode mode;
+}; \ No newline at end of file
diff --git a/Transceiver52M/ms/syncthing.cpp b/Transceiver52M/ms/syncthing.cpp
new file mode 100644
index 0000000..4f58c39
--- /dev/null
+++ b/Transceiver52M/ms/syncthing.cpp
@@ -0,0 +1,332 @@
+
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "GSMCommon.h"
+#include <atomic>
+#include <cassert>
+#include <complex>
+#include <iostream>
+#include <cstdlib>
+#include <cstdio>
+#include <thread>
+#include <fstream>
+
+#include "sigProcLib.h"
+
+#include "syncthing.h"
+#include "ms_rx_burst.h"
+#include "grgsm_vitac/grgsm_vitac.h"
+
+extern "C" {
+#include "sch.h"
+#include "convolve.h"
+#include "convert.h"
+}
+
+dummylog ms_trx::dummy_log;
+
+const int offset_start = -15;
+int offsetrange = 200;
+static int offset_ctr = 0;
+
+void tx_test(ms_trx *t, ts_hitter_q_t *q, unsigned int *tsc)
+{
+ sched_param sch_params;
+ sch_params.sched_priority = sched_get_priority_max(SCHED_FIFO);
+ pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch_params);
+
+ auto burst = genRandAccessBurst(0, 4, 0);
+ scaleVector(*burst, t->txFullScale * 0.7);
+
+ // float -> int16
+ blade_sample_type burst_buf[burst->size()];
+ convert_and_scale<int16_t, float>(burst_buf, burst->begin(), burst->size() * 2, 1);
+
+ while (1) {
+ GSM::Time target;
+ while (!q->spsc_pop(&target)) {
+ q->spsc_prep_pop();
+ }
+
+ std::cerr << std::endl << "\x1B[32m hitting " << target.FN() << "\033[0m" << std::endl;
+
+ int timing_advance = 0;
+ int64_t now_ts;
+ GSM::Time now_time;
+ target.incTN(3); // ul dl offset
+ int target_fn = target.FN();
+ int target_tn = target.TN();
+ t->timekeeper.get_both(&now_time, &now_ts);
+
+ auto diff_fn = GSM::FNDelta(target_fn, now_time.FN());
+ int diff_tn = (target_tn - (int)now_time.TN()) % 8;
+ auto tosend = GSM::Time(diff_fn, 0);
+
+ if (diff_tn > 0)
+ tosend.incTN(diff_tn);
+ else if (diff_tn < 0)
+ tosend.decTN(-diff_tn);
+
+ // in thory fn equal and tn+3 equal is also a problem...
+ if (diff_fn < 0 || (diff_fn == 0 && (now_time.TN() - target_tn < 1))) {
+ std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
+ << " tn OTHER: " << target_tn << std::endl;
+ return;
+ }
+
+ auto check = now_time + tosend;
+ int64_t send_ts =
+ now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
+
+ // std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn
+ // << " tn LOCAL: " << now_time.TN() << " tn OTHER: " << target_tn
+ // << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
+ // << " calc: " << check.FN() << ":" <<check.TN()
+ // << " target: " << target.FN() << ":" <<target.TN()
+ // << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
+
+ unsigned int pad = 4 * 25;
+ blade_sample_type buf2[burst->size() + pad];
+ memset(buf2, 0, pad * sizeof(blade_sample_type));
+ memcpy(&buf2[pad], burst_buf, burst->size() * sizeof(blade_sample_type));
+
+ assert(target.FN() == check.FN());
+ assert(target.TN() == check.TN());
+ assert(target.FN() % 51 == 21);
+
+ // auto this_offset = offset_start + (offset_ctr++ % offsetrange);
+ // std::cerr << "-- O " << this_offset << std::endl;
+ // send_ts = now_ts + ((target.FN() * 8 + (int)target.TN()) - (now_time.FN() * 8 + (int)now_time.TN())) * ONE_TS_BURST_LEN - timing_advance;
+
+ t->submit_burst_ts(buf2, burst->size() + pad, send_ts - pad);
+
+ // signalVector test(burst->size() + pad);
+ // convert_and_scale<float, int16_t>(test.begin(), buf2, burst->size() * 2 + pad, 1.f / 2047.f);
+ // estim_burst_params ebp;
+ // auto det = detectAnyBurst(test, 0, 4, 4, CorrType::RACH, 40, &ebp);
+ // if (det > 0)
+ // std::cerr << "## Y " << ebp.toa << std::endl;
+ // else
+ // std::cerr << "## NOOOOOOOOO " << ebp.toa << std::endl;
+ }
+}
+#ifdef SYNCTHINGONLY
+template <typename A> auto parsec(std::vector<std::string> &v, A &itr, std::string arg, bool *rv)
+{
+ if (*itr == arg) {
+ *rv = true;
+ return true;
+ }
+ return false;
+}
+
+template <typename A, typename B, typename C>
+bool parsec(std::vector<std::string> &v, A &itr, std::string arg, B f, C *rv)
+{
+ if (*itr == arg) {
+ itr++;
+ if (itr != v.end()) {
+ *rv = f(itr->c_str());
+ return true;
+ }
+ }
+ return false;
+}
+template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, int *rv)
+{
+ return parsec(
+ v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv);
+}
+template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, unsigned int *rv)
+{
+ return parsec(
+ v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv);
+}
+
+int main(int argc, char *argv[])
+{
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(2, &cpuset);
+
+ auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
+ if (rv < 0) {
+ std::cerr << "affinity: errreur! " << std::strerror(errno);
+ return 0;
+ }
+
+ unsigned int default_tx_freq(881000 * 1000), default_rx_freq(926000 * 1000);
+ unsigned int grx = 20, gtx = 20;
+ bool tx_flag = false;
+ pthread_setname_np(pthread_self(), "main");
+ convolve_init();
+ convert_init();
+ sigProcLibSetup();
+ initvita();
+
+ int status = 0;
+ auto trx = new ms_trx();
+ trx->do_auto_gain = true;
+
+ std::vector<std::string> args(argv + 1, argv + argc);
+ for (auto i = args.begin(); i != args.end(); ++i) {
+ parsec(args, i, "-r", 1000, &default_rx_freq);
+ parsec(args, i, "-t", 1000, &default_tx_freq);
+ parsec(args, i, "-gr", 1, &grx);
+ parsec(args, i, "-gt", 1, &gtx);
+ parsec(args, i, "-tx", &tx_flag);
+ }
+
+ std::cerr << "usage: " << argv[0] << " <rxfreq in khz, i.e. 926000> [txfreq in khz, i.e. 881000] [TX]"
+ << std::endl
+ << "rx" << (argc == 1 ? " (default) " : " ") << default_rx_freq << "hz, tx " << default_tx_freq
+ << "hz" << std::endl
+ << "gain rx " << grx << " gain tx " << gtx << std::endl
+ << (tx_flag ? "##!!## RACH TX ACTIVE ##!!##" : "-- no rach tx --") << std::endl;
+
+ status = trx->init_dev_and_streams(0, 0);
+ if (status < 0)
+ return status;
+ trx->tuneRx(default_rx_freq);
+ trx->tuneTx(default_tx_freq);
+ trx->setRxGain(grx);
+ trx->setTxGain(gtx);
+
+ if (status == 0) {
+ // FIXME: hacks! needs exit flag for detached threads!
+
+ std::thread(rcv_bursts_test, &trx->rxqueue, &trx->mTSC).detach();
+ if (tx_flag)
+ std::thread(tx_test, trx, &trx->ts_hitter_q, &trx->mTSC).detach();
+ trx->start();
+ do {
+ sleep(1);
+ } while (1);
+
+ trx->stop_threads();
+ }
+ delete trx;
+
+ return status;
+}
+#endif
+
+int ms_trx::init_streams(void *rx_cb, void *tx_cb)
+{
+ return 0;
+}
+
+int ms_trx::init_dev_and_streams(void *rx_cb, void *tx_cb)
+{
+ int status = 0;
+ status = base::init_device(rx_bh(), tx_bh());
+ if (status < 0) {
+ std::cerr << "failed to init dev!" << std::endl;
+ return -1;
+ }
+ return status;
+}
+
+bh_fn_t ms_trx::rx_bh()
+{
+ return [this](dev_buf_t *rcd) -> int {
+ if (this->search_for_sch(rcd) == SCH_STATE::FOUND)
+ this->grab_bursts(rcd);
+ return 0;
+ };
+}
+
+bh_fn_t ms_trx::tx_bh()
+{
+ return [this](dev_buf_t *rcd) -> int {
+#pragma unused(rcd)
+ auto y = this;
+#pragma unused(y)
+ /* nothing to do here */
+ return 0;
+ };
+}
+
+void ms_trx::start()
+{
+ auto fn = get_rx_burst_handler_fn(rx_bh());
+ rx_task = std::thread(fn);
+
+ usleep(1000);
+ auto fn2 = get_tx_burst_handler_fn(tx_bh());
+ tx_task = std::thread(fn2);
+}
+
+void ms_trx::stop_threads()
+{
+ std::cerr << "killing threads...\r\n" << std::endl;
+ rx_task.join();
+}
+
+void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target)
+{
+ int64_t now_ts;
+ GSM::Time now_time;
+ target.incTN(3); // ul dl offset
+ int target_fn = target.FN();
+ int target_tn = target.TN();
+ timekeeper.get_both(&now_time, &now_ts);
+
+ auto diff_fn = GSM::FNDelta(target_fn, now_time.FN());
+ int diff_tn = (target_tn - (int)now_time.TN()) % 8;
+ auto tosend = GSM::Time(diff_fn, 0);
+
+ if (diff_tn > 0)
+ tosend.incTN(diff_tn);
+ else
+ tosend.decTN(-diff_tn);
+
+ // in thory fn equal and tn+3 equal is also a problem...
+ if (diff_fn < 0 || (diff_fn == 0 && (now_time.TN() - target_tn < 1))) {
+ std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
+ << " tn OTHER: " << target_tn << std::endl;
+ return;
+ }
+
+ auto check = now_time + tosend;
+ int64_t send_ts = now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
+
+ // std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn
+ // << " tn LOCAL/OTHER: " << now_time.TN() << "/" << target_tn
+ // << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
+ // << " check: " << check.FN() << ":" <<check.TN()
+ // << " target: " << target.FN() << ":" <<target.TN()
+ // << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
+
+#if 1
+ unsigned int pad = 4 * 4;
+ blade_sample_type buf2[len + pad];
+ memset(buf2, 0, pad * sizeof(blade_sample_type));
+ memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type));
+
+ assert(target.FN() == check.FN());
+ assert(target.TN() == check.TN());
+ submit_burst_ts(buf2, len + pad, send_ts - pad);
+#else
+ submit_burst_ts(buffer, len, send_ts);
+#endif
+}
diff --git a/Transceiver52M/ms/syncthing.h b/Transceiver52M/ms/syncthing.h
new file mode 100644
index 0000000..8fb4aeb
--- /dev/null
+++ b/Transceiver52M/ms/syncthing.h
@@ -0,0 +1,224 @@
+#pragma once
+
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <atomic>
+#include <cassert>
+#include <complex>
+#include <cstdint>
+#include <mutex>
+#include <iostream>
+#include <thread>
+
+#if defined(BUILDBLADE)
+#include "bladerf_specific.h"
+#define BASET blade_hw<ms_trx>
+#elif defined(BUILDUHD)
+#include "uhd_specific.h"
+#define BASET uhd_hw<ms_trx>
+#else
+#error wat? no device..
+#endif
+
+#include "GSMCommon.h"
+#include "itrq.h"
+
+const unsigned int ONE_TS_BURST_LEN = (3 + 58 + 26 + 58 + 3 + 8.25) * 4 /*sps*/;
+const unsigned int NUM_RXQ_FRAMES = 12 * 1; // rx thread <-> upper rx queue
+const unsigned int SCH_LEN_SPS = (ONE_TS_BURST_LEN * 8 /*ts*/ * 12 /*frames*/);
+
+template <typename T> void clamp_array(T *start2, unsigned int len, T max)
+{
+ for (int i = 0; i < len; i++) {
+ const T t1 = start2[i] < -max ? -max : start2[i];
+ const T t2 = t1 > max ? max : t1;
+ start2[i] = t2;
+ }
+}
+template <typename DST_T, typename SRC_T, typename ST>
+void convert_and_scale(void *dst, void *src, unsigned int src_len, ST scale)
+{
+ for (unsigned int i = 0; i < src_len; i++)
+ reinterpret_cast<DST_T *>(dst)[i] = static_cast<DST_T>((reinterpret_cast<SRC_T *>(src)[i])) * scale;
+}
+template <typename DST_T, typename SRC_T> void convert_and_scale_default(void *dst, void *src, unsigned int src_len)
+{
+ return convert_and_scale<DST_T, SRC_T>(dst, src, src_len, SAMPLE_SCALE_FACTOR);
+}
+
+struct one_burst {
+ GSM::Time gsmts;
+ blade_sample_type burst[ONE_TS_BURST_LEN];
+};
+
+using rx_queue_t = spsc_cond<8 * NUM_RXQ_FRAMES, one_burst, true, false>;
+
+enum class SCH_STATE { SEARCHING, FOUND };
+
+class dummylog : private std::streambuf {
+ std::ostream null_stream;
+
+ public:
+ dummylog() : null_stream(this){};
+ ~dummylog() override{};
+ std::ostream &operator()()
+ {
+ return null_stream;
+ }
+ int overflow(int c) override
+ {
+ return c;
+ }
+};
+
+// keeps relationship between gsm time and (continuously adjusted) ts
+class time_keeper {
+ GSM::Time global_time_keeper;
+ int64_t global_ts_keeper;
+ std::mutex m;
+
+ public:
+ time_keeper() : global_time_keeper(0), global_ts_keeper(0)
+ {
+ }
+
+ void set(GSM::Time t, int64_t ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper = t;
+ global_ts_keeper = ts;
+ }
+ void inc_both()
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper.incTN(1);
+ global_ts_keeper += ONE_TS_BURST_LEN;
+ }
+ void inc_and_update(int64_t new_ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper.incTN(1);
+ global_ts_keeper = new_ts;
+ // std::cerr << "u " << new_ts << std::endl;
+ }
+ void inc_and_update_safe(int64_t new_ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ auto diff = new_ts - global_ts_keeper;
+ assert(diff < 1.5 * ONE_TS_BURST_LEN);
+ assert(diff > 0.5 * ONE_TS_BURST_LEN);
+ global_time_keeper.incTN(1);
+ global_ts_keeper = new_ts;
+ // std::cerr << "s " << new_ts << std::endl;
+ }
+ void dec_by_one()
+ {
+ std::lock_guard<std::mutex> g(m);
+ global_time_keeper.decTN(1);
+ global_ts_keeper -= ONE_TS_BURST_LEN;
+ }
+ auto get_ts()
+ {
+ std::lock_guard<std::mutex> g(m);
+ return global_ts_keeper;
+ }
+ auto gsmtime()
+ {
+ std::lock_guard<std::mutex> g(m);
+ return global_time_keeper;
+ }
+ void get_both(GSM::Time *t, int64_t *ts)
+ {
+ std::lock_guard<std::mutex> g(m);
+ *t = global_time_keeper;
+ *ts = global_ts_keeper;
+ }
+};
+
+using ts_hitter_q_t = spsc_cond<64, GSM::Time, true, false>;
+
+struct ms_trx : public BASET {
+ using base = BASET;
+ static dummylog dummy_log;
+ unsigned int mTSC;
+ unsigned int mBSIC;
+ int timing_advance;
+ bool do_auto_gain;
+
+ std::thread rx_task;
+ std::thread tx_task;
+ std::thread *calcrval_task;
+
+ // provides bursts to upper rx thread
+ rx_queue_t rxqueue;
+#ifdef SYNCTHINGONLY
+ ts_hitter_q_t ts_hitter_q;
+#endif
+ blade_sample_type *first_sch_buf;
+ blade_sample_type *burst_copy_buffer;
+
+ uint64_t first_sch_buf_rcv_ts;
+ std::atomic<bool> rcv_done;
+ std::atomic<bool> sch_thread_done;
+
+ int64_t temp_ts_corr_offset = 0;
+ int64_t first_sch_ts_start = -1;
+
+ time_keeper timekeeper;
+
+ void start();
+
+ bool handle_sch_or_nb(bool first = false);
+ bool handle_sch(bool first = false);
+ bool decode_sch(float *bits, bool update_global_clock);
+ SCH_STATE search_for_sch(dev_buf_t *rcd);
+ void grab_bursts(dev_buf_t *rcd);
+
+ int init_device();
+ int init_streams(void *rx_cb, void *tx_cb);
+ int init_dev_and_streams(void *rx_cb, void *tx_cb);
+ void stop_threads();
+ void *rx_cb(ms_trx *t);
+ void *tx_cb();
+ void maybe_update_gain(one_burst &brst);
+
+ ms_trx()
+ : timing_advance(0), do_auto_gain(false), rxqueue(), first_sch_buf(new blade_sample_type[SCH_LEN_SPS]),
+ burst_copy_buffer(new blade_sample_type[ONE_TS_BURST_LEN]), rcv_done{ false }, sch_thread_done{ false }
+ {
+ }
+
+ virtual ~ms_trx()
+ {
+ delete[] burst_copy_buffer;
+ delete[] first_sch_buf;
+ }
+ bh_fn_t rx_bh();
+ bh_fn_t tx_bh();
+
+ void submit_burst(blade_sample_type *buffer, int len, GSM::Time);
+ void set_ta(int val)
+ {
+ assert(val > -127 && val < 128);
+ timing_advance = val * 4;
+ }
+};
diff --git a/Transceiver52M/ms/uhd_specific.h b/Transceiver52M/ms/uhd_specific.h
new file mode 100644
index 0000000..88b9a1b
--- /dev/null
+++ b/Transceiver52M/ms/uhd_specific.h
@@ -0,0 +1,274 @@
+#pragma once
+
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <uhd/version.hpp>
+#include <uhd/usrp/multi_usrp.hpp>
+#include <uhd/types/metadata.hpp>
+#include <complex>
+#include <cstring>
+#include <iostream>
+#include <thread>
+
+#include <Timeval.h>
+#include <vector>
+
+using blade_sample_type = std::complex<int16_t>;
+const int SAMPLE_SCALE_FACTOR = 1;
+
+struct uhd_buf_wrap {
+ double rxticks;
+ size_t num_samps;
+ uhd::rx_metadata_t *md;
+ blade_sample_type *buf;
+ auto actual_samples_per_buffer()
+ {
+ return num_samps;
+ }
+ long get_first_ts()
+ {
+ return md->time_spec.to_ticks(rxticks);
+ }
+ int readall(blade_sample_type *outaddr)
+ {
+ memcpy(outaddr, buf, num_samps * sizeof(blade_sample_type));
+ return num_samps;
+ }
+ int read_n(blade_sample_type *outaddr, int start, int num)
+ {
+ assert(start >= 0);
+ auto to_read = std::min((int)num_samps - start, num);
+ assert(to_read >= 0);
+ memcpy(outaddr, buf + start, to_read * sizeof(blade_sample_type));
+ return to_read;
+ }
+};
+
+using dev_buf_t = uhd_buf_wrap;
+using bh_fn_t = std::function<int(dev_buf_t *)>;
+
+template <typename T> struct uhd_hw {
+ uhd::usrp::multi_usrp::sptr dev;
+ uhd::rx_streamer::sptr rx_stream;
+ uhd::tx_streamer::sptr tx_stream;
+ blade_sample_type *one_pkt_buf;
+ std::vector<blade_sample_type *> pkt_ptrs;
+ size_t rx_spp;
+ double rxticks;
+ unsigned int rxFullScale, txFullScale;
+ int rxtxdelay;
+ float rxgain, txgain;
+
+ virtual ~uhd_hw()
+ {
+ delete[] one_pkt_buf;
+ }
+ uhd_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67)
+ {
+ }
+
+ bool tuneTx(double freq, size_t chan = 0)
+ {
+ msleep(25);
+ dev->set_tx_freq(freq, chan);
+ msleep(25);
+ return true;
+ };
+ bool tuneRx(double freq, size_t chan = 0)
+ {
+ msleep(25);
+ dev->set_rx_freq(freq, chan);
+ msleep(25);
+ return true;
+ };
+ bool tuneRxOffset(double offset, size_t chan = 0)
+ {
+ return true;
+ };
+
+ double setRxGain(double dB, size_t chan = 0)
+ {
+ rxgain = dB;
+ msleep(25);
+ dev->set_rx_gain(dB, chan);
+ msleep(25);
+ return dB;
+ };
+ double setTxGain(double dB, size_t chan = 0)
+ {
+ txgain = dB;
+ msleep(25);
+ dev->set_tx_gain(dB, chan);
+ msleep(25);
+ return dB;
+ };
+ int setPowerAttenuation(int atten, size_t chan = 0)
+ {
+ return atten;
+ };
+
+ int init_device(bh_fn_t rxh, bh_fn_t txh)
+ {
+ auto const lock_delay_ms = 500;
+ auto const mcr = 26e6;
+ auto const rate = (1625e3 / 6) * 4;
+ auto const ref = "external";
+ auto const gain = 35;
+ auto const freq = 931.4e6; // 936.8e6
+ auto bw = 0.5e6;
+ auto const channel = 0;
+ std::string args = {};
+
+ dev = uhd::usrp::multi_usrp::make(args);
+ std::cout << "Using Device: " << dev->get_pp_string() << std::endl;
+ dev->set_clock_source(ref);
+ dev->set_master_clock_rate(mcr);
+ dev->set_rx_rate(rate, channel);
+ dev->set_tx_rate(rate, channel);
+ uhd::tune_request_t tune_request(freq, 0);
+ dev->set_rx_freq(tune_request, channel);
+ dev->set_rx_gain(gain, channel);
+ dev->set_tx_gain(60, channel);
+ dev->set_rx_bandwidth(bw, channel);
+ dev->set_tx_bandwidth(bw, channel);
+
+ while (!(dev->get_rx_sensor("lo_locked", channel).to_bool() &&
+ dev->get_mboard_sensor("ref_locked").to_bool()))
+ std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms));
+
+ uhd::stream_args_t stream_args("sc16", "sc16");
+ rx_stream = dev->get_rx_stream(stream_args);
+ uhd::stream_args_t stream_args2("sc16", "sc16");
+ tx_stream = dev->get_tx_stream(stream_args2);
+
+ rx_spp = rx_stream->get_max_num_samps();
+ rxticks = dev->get_rx_rate();
+ assert(rxticks == dev->get_tx_rate());
+ one_pkt_buf = new blade_sample_type[rx_spp];
+ pkt_ptrs = { 1, &one_pkt_buf[0] };
+ return 0;
+ }
+
+ void *rx_cb(bh_fn_t burst_handler)
+ {
+ void *ret;
+ static int to_skip = 0;
+
+ uhd::rx_metadata_t md;
+ auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 3.0, true);
+
+ if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
+ std::cerr << boost::format("Timeout while streaming") << std::endl;
+ exit(0);
+ }
+ if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) {
+ std::cerr << boost::format("Got an overflow indication. Please consider the following:\n"
+ " Your write medium must sustain a rate of %fMB/s.\n"
+ " Dropped samples will not be written to the file.\n"
+ " Please modify this example for your purposes.\n"
+ " This message will not appear again.\n") %
+ 1.f;
+ exit(0);
+ ;
+ }
+ if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) {
+ std::cerr << str(boost::format("Receiver error: %s") % md.strerror());
+ exit(0);
+ }
+
+ dev_buf_t rcd = { rxticks, num_rx_samps, &md, &one_pkt_buf[0] };
+
+ if (to_skip < 120) // prevents weird overflows on startup
+ to_skip++;
+ else {
+ burst_handler(&rcd);
+ }
+
+ return ret;
+ }
+
+ auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [this, burst_handler] {
+ pthread_setname_np(pthread_self(), "rxrun");
+
+ uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
+ stream_cmd.stream_now = true;
+ stream_cmd.time_spec = uhd::time_spec_t();
+ rx_stream->issue_stream_cmd(stream_cmd);
+
+ while (1) {
+ rx_cb(burst_handler);
+ }
+ };
+ return fn;
+ }
+ auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [] {
+ // dummy
+ };
+ return fn;
+ }
+ void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
+ {
+ uhd::tx_metadata_t m = {};
+ m.end_of_burst = true;
+ m.start_of_burst = true;
+ m.has_time_spec = true;
+ m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay!
+ std::vector<void *> ptrs(1, buffer);
+
+ tx_stream->send(ptrs, len, m);
+
+ uhd::async_metadata_t async_md;
+ bool tx_ack = false;
+ while (!tx_ack && tx_stream->recv_async_msg(async_md)) {
+ tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK);
+ }
+ std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl;
+ }
+
+ void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
+ {
+ pthread_setname_np(pthread_self(), name);
+
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpunum, &cpuset);
+
+ auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
+ if (rv < 0) {
+ std::cerr << name << " affinity: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+
+ sched_param sch_params;
+ sch_params.sched_priority = prio;
+ rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
+ if (rv < 0) {
+ std::cerr << name << " sched: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+ }
+}; \ No newline at end of file
diff --git a/Transceiver52M/sch.c b/Transceiver52M/sch.c
index 5d2b5c6..9d81558 100644
--- a/Transceiver52M/sch.c
+++ b/Transceiver52M/sch.c
@@ -7,6 +7,8 @@
#include <osmocom/core/conv.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/crcgen.h>
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_parity.h>
#include "sch.h"
@@ -52,13 +54,6 @@ static const struct osmo_conv_code gsm_conv_sch = {
.next_state = sch_next_state,
};
-const struct osmo_crc16gen_code gsm0503_sch_crc10 = {
- .bits = 10,
- .poly = 0x175,
- .init = 0x000,
- .remainder = 0x3ff,
-};
-
#define GSM_MAX_BURST_LEN 157 * 4
#define GSM_SYM_RATE (1625e3 / 6) * 4
@@ -157,7 +152,7 @@ int gsm_sch_parse(const uint8_t *info, struct sch_info *desc)
}
/* From osmo-bts */
-int gsm_sch_decode(uint8_t *info, sbit_t *data)
+__attribute__((xray_always_instrument)) __attribute__((noinline)) int gsm_sch_decode(uint8_t *info, sbit_t *data)
{
int rc;
ubit_t uncoded[GSM_SCH_UNCODED_LEN];
diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp
index c2cf2c9..51f7599 100644
--- a/Transceiver52M/sigProcLib.cpp
+++ b/Transceiver52M/sigProcLib.cpp
@@ -131,6 +131,7 @@ static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,N
static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL};
static CorrelationSequence *gSCHSequence = NULL;
+static CorrelationSequence *gDummySequence = NULL;
static PulseSequence *GSMPulse1 = NULL;
static PulseSequence *GSMPulse4 = NULL;
@@ -156,6 +157,9 @@ void sigProcLibDestroy()
delete gSCHSequence;
gSCHSequence = NULL;
+ delete gDummySequence;
+ gDummySequence = NULL;
+
delete GMSKRotation1;
delete GMSKReverseRotation1;
delete GMSKRotation4;
@@ -1297,6 +1301,77 @@ release:
return status;
}
+static bool generateDummyMidamble(int sps)
+{
+ bool status = true;
+ float toa;
+ complex *data = NULL;
+ signalVector *autocorr = NULL, *midamble = NULL;
+ signalVector *midMidamble = NULL, *_midMidamble = NULL;
+
+ delete gDummySequence;
+
+ /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */
+ midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true);
+ if (!midMidamble)
+ return false;
+
+ /* Simulated receive sequence is pulse shaped */
+ midamble = modulateBurst(gDummyBurstTSC, 0, sps, false);
+ if (!midamble) {
+ status = false;
+ goto release;
+ }
+
+ // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst,
+ // the ideal TSC has an + 180 degree phase shift,
+ // due to the pi/2 frequency shift, that
+ // needs to be accounted for.
+ // 26-midamble is 61 symbols into burst, has +90 degree phase shift.
+ scaleVector(*midMidamble, complex(-1.0, 0.0));
+ scaleVector(*midamble, complex(0.0, 1.0));
+
+ conjugateVector(*midMidamble);
+
+ /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
+ data = (complex *) convolve_h_alloc(midMidamble->size());
+ _midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free);
+ _midMidamble->setAligned(true);
+ midMidamble->copyTo(*_midMidamble);
+
+ autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY);
+ if (!autocorr) {
+ status = false;
+ goto release;
+ }
+
+ gDummySequence = new CorrelationSequence;
+ gDummySequence->sequence = _midMidamble;
+ gDummySequence->gain = peakDetect(*autocorr, &toa, NULL);
+
+ /* For 1 sps only
+ * (Half of correlation length - 1) + midpoint of pulse shape + remainder
+ * 13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2
+ */
+ if (sps == 1)
+ gDummySequence->toa = toa - 13.5;
+ else
+ gDummySequence->toa = 0;
+
+release:
+ delete autocorr;
+ delete midamble;
+ delete midMidamble;
+
+ if (!status) {
+ delete _midMidamble;
+ free(data);
+ gDummySequence = NULL;
+ }
+
+ return status;
+}
+
static CorrelationSequence *generateEdgeMidamble(int tsc)
{
complex *data = NULL;
@@ -1513,17 +1588,18 @@ float energyDetect(const signalVector &rxBurst, unsigned windowLength)
return energy/windowLength;
}
-static signalVector *downsampleBurst(const signalVector &burst)
+static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN,
+ int out_len = DOWNSAMPLE_OUT_LEN)
{
- signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len());
- signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN);
- burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN);
-
- if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN,
- (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
- delete out;
- out = NULL;
- }
+ signalVector in(in_len, dnsampler->len());
+ // gSCHSequence->sequence->size(), ensure next conv has no realloc
+ signalVector *out = new signalVector(out_len, 64);
+ burst.copyToSegment(in, 0, in_len);
+
+ if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) {
+ delete out;
+ out = NULL;
+ }
return out;
};
@@ -1544,6 +1620,9 @@ static float computeCI(const signalVector *burst, const CorrelationSequence *syn
if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync
return 0;
+ if (ps + N > burst->size())
+ return 0;
+
/* Estimate Signal power */
S = 0.0f;
for (int i=0, j=ps; i<(int)N; i++,j++)
@@ -1586,11 +1665,11 @@ static int detectBurst(const signalVector &burst,
corr_in = &burst;
break;
case 4:
- dec = downsampleBurst(burst);
- /* Running at the downsampled rate at this point: */
- corr_in = dec;
- sps = 1;
- break;
+ dec = downsampleBurst(burst, len * 4, len);
+ /* Running at the downsampled rate at this point: */
+ corr_in = dec;
+ sps = 1;
+ break;
default:
osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps);
}
@@ -1668,11 +1747,11 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
// Detect potential clipping
// We still may be able to demod the burst, so we'll give it a try
// and only report clipping if we can't demod.
- float maxAmpl = maxAmplitude(rxBurst);
- if (maxAmpl > CLIP_THRESH) {
- LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
- clipping = true;
- }
+ // float maxAmpl = maxAmplitude(rxBurst);
+ // if (maxAmpl > CLIP_THRESH) {
+ // LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
+ // clipping = true;
+ // }
start = target - head - 1;
len = head + tail;
@@ -1735,7 +1814,6 @@ int detectSCHBurst(signalVector &burst,
int rc, start, target, head, tail, len;
float _toa;
complex _amp;
- signalVector *corr, *_burst;
CorrelationSequence *sync;
if ((sps != 1) && (sps != 4))
@@ -1748,6 +1826,11 @@ int detectSCHBurst(signalVector &burst,
head = 4;
tail = 4;
break;
+ case sch_detect_type::SCH_DETECT_BUFFER:
+ target = 1;
+ head = 0;
+ tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps
+ break;
case sch_detect_type::SCH_DETECT_FULL:
default:
head = target - 1;
@@ -1758,19 +1841,16 @@ int detectSCHBurst(signalVector &burst,
start = (target - head) * 1 - 1;
len = (head + tail) * 1;
sync = gSCHSequence;
- corr = new signalVector(len);
+ signalVector corr(len);
- _burst = new signalVector(burst, sync->sequence->size(), 5);
+ signalVector _burst(burst, sync->sequence->size(), 5);
- memcpy(_burst->begin() - sync->sequence->size(), sync->history,
- sync->sequence->size() * sizeof(complex));
+ memcpy(_burst.begin() - sync->sequence->size(), sync->history, sync->sequence->size() * sizeof(complex));
memcpy(sync->history, &burst.begin()[burst.size() - sync->sequence->size()],
sync->sequence->size() * sizeof(complex));
- rc = detectBurst(*_burst, *corr, sync,
- thresh, sps, start, len, ebp);
- delete corr;
+ rc = detectBurst(_burst, corr, sync, thresh, sps, start, len, ebp);
if (rc < 0) {
return -1;
@@ -1780,12 +1860,31 @@ int detectSCHBurst(signalVector &burst,
return 0;
}
- /* Subtract forward search bits from delay */
- ebp->toa = ebp->toa - head;
+ if (state == sch_detect_type::SCH_DETECT_BUFFER)
+ ebp->toa = ebp->toa - (3 + 39 + 64);
+ else {
+ /* Subtract forward search bits from delay */
+ ebp->toa = ebp->toa - head;
+ }
return rc;
}
+static int detectDummyBurst(const signalVector &burst, float threshold,
+ int sps, unsigned max_toa, struct estim_burst_params *ebp)
+{
+ int rc, target, head, tail;
+ CorrelationSequence *sync;
+
+ target = 3 + 58 + 16 + 5;
+ head = 10;
+ tail = 6 + max_toa;
+ sync = gDummySequence;
+
+ ebp->tsc = 0;
+ rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp);
+ return rc;
+}
/*
* Normal burst detection
@@ -1854,6 +1953,9 @@ int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold,
case RACH:
rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp);
break;
+ case IDLE:
+ rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp);
+ break;
default:
LOG(ERR) << "Invalid correlation type";
}
@@ -2057,6 +2159,7 @@ bool sigProcLibSetup()
generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
generateSCHSequence(1);
+ generateDummyMidamble(1);
for (int tsc = 0; tsc < 8; tsc++) {
generateMidamble(1, tsc);
diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h
index 73d4704..39c8ddd 100644
--- a/Transceiver52M/sigProcLib.h
+++ b/Transceiver52M/sigProcLib.h
@@ -94,6 +94,8 @@ signalVector *generateDummyBurst(int sps, int tn);
void scaleVector(signalVector &x,
complex scale);
+signalVector *delayVector(const signalVector *in, signalVector *out, float delay);
+
/**
Rough energy estimator.
@param rxBurst A GSM burst.
@@ -137,6 +139,7 @@ int detectAnyBurst(const signalVector &burst,
enum class sch_detect_type {
SCH_DETECT_FULL,
SCH_DETECT_NARROW,
+ SCH_DETECT_BUFFER,
};
int detectSCHBurst(signalVector &rxBurst,
diff --git a/trxcon/l1ctl.c b/trxcon/l1ctl.c
index f39bf03..8853c63 100644
--- a/trxcon/l1ctl.c
+++ b/trxcon/l1ctl.c
@@ -353,14 +353,14 @@ static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
l1l->fbsb_conf_sent = false;
/* Only if current ARFCN differs */
-// if (l1l->trx->band_arfcn != band_arfcn) {
+ if (l1l->trx->band_arfcn != band_arfcn) {
/* Update current ARFCN */
l1l->trx->band_arfcn = band_arfcn;
/* Tune transceiver to required ARFCN */
trx_if_cmd_rxtune(l1l->trx, band_arfcn);
trx_if_cmd_txtune(l1l->trx, band_arfcn);
-// }
+ }
/* Transceiver might have been powered on before, e.g.
* in case of sending L1CTL_FBSB_REQ due to signal loss. */
@@ -373,8 +373,7 @@ static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
l1l->fbsb_timer.data = l1l;
l1l->fbsb_timer.cb = fbsb_timer_cb;
LOGP(DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", timeout * GSM_TDMA_FN_DURATION_uS / 1000);
- osmo_timer_schedule(&l1l->fbsb_timer, 35,
- timeout * GSM_TDMA_FN_DURATION_uS);
+ osmo_timer_schedule(&l1l->fbsb_timer, 2, timeout * GSM_TDMA_FN_DURATION_uS);
exit:
msgb_free(msg);
diff --git a/trxcon/logging.c b/trxcon/logging.c
index 78915f2..0a4c08a 100644
--- a/trxcon/logging.c
+++ b/trxcon/logging.c
@@ -62,7 +62,7 @@ static struct log_info_cat trx_log_info_cat[] = {
.name = "DSCH",
.description = "Scheduler management",
.color = "\033[1;36m",
- .enabled = 1, .loglevel = LOGL_NOTICE,
+ .enabled = 0, .loglevel = LOGL_NOTICE,
},
[DSCHD] = {
.name = "DSCHD",
diff --git a/trxcon/trx_if.c b/trxcon/trx_if.c
index 84694c8..ffad6c0 100644
--- a/trxcon/trx_if.c
+++ b/trxcon/trx_if.c
@@ -47,7 +47,9 @@
#include "logging.h"
#include "scheduler.h"
+#ifdef IPCIF
#include "../Transceiver52M/l1if.h"
+#endif
static struct value_string trx_evt_names[] = {
{ 0, NULL } /* no events? */
@@ -146,13 +148,19 @@ static void trx_ctrl_send(struct trx_instance *trx)
return;
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+#ifdef IPCIF
char* cmd = malloc(TRXC_BUF_SIZE);
memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
/* Send command */
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
trxif_to_trx_c(cmd);
-// send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
+
+#else
+ /* Send command */
+ LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
+ send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
+#endif
/* Trigger state machine */
if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
@@ -476,7 +484,9 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
struct trx_ctrl_msg *tcm;
int resp, rsp_len;
char buf[TRXC_BUF_SIZE], *p;
+ ssize_t read_len;
+#ifdef IPCIF
char* response = trxif_from_trx_c();
if (!response) {
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
@@ -484,6 +494,14 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
}
memcpy(buf, response, TRXC_BUF_SIZE);
free(response);
+#else
+ read_len = read(ofd->fd, buf, sizeof(buf) - 1);
+ if (read_len <= 0) {
+ LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
+ return read_len;
+ }
+ buf[read_len] = '\0';
+#endif
if (!!strncmp(buf, "RSP ", 4)) {
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
@@ -592,7 +610,7 @@ static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
uint32_t fn;
ssize_t read_len;
-
+#ifdef IPCIF
struct trxd_from_trx* rcvd = trxif_from_trx_d();
if (!rcvd) {
LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
@@ -609,6 +627,29 @@ static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
memcpy(bits, rcvd->symbols, 148);
free(rcvd);
+#else
+ read_len = read(ofd->fd, buf, sizeof(buf));
+ if (read_len <= 0) {
+ LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
+ return read_len;
+ }
+
+ if (read_len != 158) {
+ LOGP(DTRXD, LOGL_ERROR,
+ "Got data message with invalid "
+ "length '%zd'\n",
+ read_len);
+ return -EINVAL;
+ }
+#endif
+ tn = buf[0];
+ fn = osmo_load32be(buf + 1);
+ rssi = -(int8_t)buf[5];
+ toa256 = ((int16_t)(buf[6] << 8) | buf[7]);
+
+ /* Copy and convert bits {254..0} to sbits {-127..127} */
+ //osmo_ubit2sbit(bits, buf + 8, 148);
+ memcpy(bits, buf + 8, 148);
if (tn >= 8) {
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
@@ -643,12 +684,44 @@ static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
uint8_t pwr, const ubit_t *bits)
{
+#ifdef IPCIF
struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
t->ts = tn;
t->fn = fn;
t->txlev = pwr;
memcpy(t->symbols, bits, 148);
trxif_to_trx_d(t);
+#else
+ uint8_t buf[TRXD_BUF_SIZE];
+
+ /**
+ * We must be sure that we have clock,
+ * and we have sent all control data
+ *
+ * TODO: introduce proper state machines for both
+ * transceiver and its TRXC interface.
+ */
+#if 0
+ if (trx->fsm->state != TRX_STATE_ACTIVE) {
+ LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
+ "transceiver isn't ready\n");
+ return -EAGAIN;
+ }
+#endif
+
+ LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
+
+ buf[0] = tn;
+ osmo_store32be(fn, buf + 1);
+ buf[5] = pwr;
+
+ /* Copy ubits {0,1} */
+ memcpy(buf + 6, bits, 148);
+
+ /* Send data to transceiver */
+ send(trx->trx_ofd_data.fd, buf, 154, 0);
+
+#endif
return 0;
}
@@ -683,6 +756,7 @@ struct trx_instance *trx_if_open(void *tall_ctx,
/* Initialize CTRL queue */
INIT_LLIST_HEAD(&trx->trx_ctrl_list);
+#ifdef IPCIF
rc = eventfd(0, 0);
osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
osmo_fd_register(get_c_fd());
@@ -690,8 +764,25 @@ struct trx_instance *trx_if_open(void *tall_ctx,
rc = eventfd(0, 0);
osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
osmo_fd_register(get_d_fd());
-
+#else
+ /* Open sockets */
+ rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host, base_port + 101, remote_host, base_port + 1,
+ trx_ctrl_read_cb);
+ if (rc < 0)
+ goto udp_error;
+
+ rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host, base_port + 102, remote_host, base_port + 2,
+ trx_data_rx_cb);
+ if (rc < 0)
+ goto udp_error;
+#endif
return trx;
+
+udp_error:
+ LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
+ osmo_fsm_inst_free(trx->fsm);
+ talloc_free(trx);
+ return NULL;
}
/* Flush pending control messages */
@@ -723,8 +814,13 @@ void trx_if_close(struct trx_instance *trx)
trx_if_flush_ctrl(trx);
/* Close sockets */
+#ifdef IPCIF
close(get_c_fd()->fd);
close(get_d_fd()->fd);
+#else
+ trx_udp_close(&trx->trx_ofd_ctrl);
+ trx_udp_close(&trx->trx_ofd_data);
+#endif
/* Free memory */
osmo_fsm_inst_free(trx->fsm);
diff --git a/trxcon/trx_if.h b/trxcon/trx_if.h
index 58f76ed..abbde97 100644
--- a/trxcon/trx_if.h
+++ b/trxcon/trx_if.h
@@ -22,8 +22,10 @@ enum trx_fsm_states {
};
struct trx_instance {
-// struct osmo_fd trx_ofd_ctrl;
-// struct osmo_fd trx_ofd_data;
+#ifndef IPCIF
+ struct osmo_fd trx_ofd_ctrl;
+ struct osmo_fd trx_ofd_data;
+#endif
struct osmo_timer_list trx_ctrl_timer;
struct llist_head trx_ctrl_list;
diff --git a/trxcon/trxcon.c b/trxcon/trxcon.c
index 3005a93..d5fd6e1 100644
--- a/trxcon/trxcon.c
+++ b/trxcon/trxcon.c
@@ -21,6 +21,7 @@
*
*/
+#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@@ -29,6 +30,7 @@
#include <unistd.h>
#include <signal.h>
#include <time.h>
+#include <pthread.h>
#include <arpa/inet.h>
@@ -277,6 +279,21 @@ int main(int argc, char **argv)
{
int rc = 0;
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(3, &cpuset);
+ pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
+
+ int prio = sched_get_priority_max(SCHED_RR) - 5;
+ struct sched_param param;
+ param.sched_priority = prio;
+ int rv = sched_setscheduler(0, SCHED_RR, &param);
+ if (rv < 0) {
+ LOGP(DAPP, LOGL_ERROR, "Failed to set sched!\n");
+ exit(0);
+ }
+
printf("%s", COPYRIGHT);
init_defaults();
handle_options(argc, argv);