Change-Id: I7c5abe57182e7ef508cac4068c0b41f905d39fd6
This commit is contained in:
Eric Wild 2022-06-06 00:48:09 +02:00
parent f590eeb436
commit 935c8cb7c9
31 changed files with 3514 additions and 314 deletions

6
.gitignore vendored
View File

@ -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

View File

@ -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) */

View File

@ -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;

View File

@ -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
#

View File

@ -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;

View File

@ -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.;
start[i] *= 1./2047.;
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;
auto ss = reinterpret_cast<std::complex<float>*>(burst->begin());
// printme = ret >= 0 ? true : false;
// 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) {
// 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);
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 */

View File

@ -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

View File

@ -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();
if (strongest_window_nr < 0)
strongest_window_nr = 0;
// 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;
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);
}

View File

@ -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;
};

204
Transceiver52M/itrq.h Normal file
View File

@ -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> {};

View File

@ -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;
};

View File

@ -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

View File

@ -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);
}
}
};

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
};
};

View File

@ -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;
};

View File

@ -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
}

View File

@ -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;
}
};

View File

@ -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);
}
}
};

View File

@ -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];

View File

@ -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);
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(), DOWNSAMPLE_IN_LEN,
(float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
delete out;
out = NULL;
}
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);

View File

@ -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,

View File

@ -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);

View File

@ -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",

View File

@ -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);

View File

@ -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;

View File

@ -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);