aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric <ewild@sysmocom.de>2022-11-28 19:21:08 +0100
committerEric <ewild@sysmocom.de>2022-12-01 12:00:04 +0100
commita0e1ed3215a142ded2a8a9c51c88e310885d6429 (patch)
tree06a04a7d1504bd63b4abd6eb89c89387253d14d7
parenta5439daaf7ddd7c189bf075a9aa03a6a26431d6c (diff)
ms-trx supportmstx_newtrxcon
This is basically a trxcon that includes a transceiver, and can just be used with existing and future apps supporting the trxcon interface, i.e. mobile or ccch_scan. Supports bladerf and uhd, binaries are automatically built if those libs are detected + the osmocom-bb submodule exists. Currently using hardcoded sched/prios aimed at a setup with working, reliable usb and reserved cores, for example a raspi 4 (ONLY 4, not 3, not 2, not any other version) Additionally builds test tools used for development: osmo-trx-syncthing* Change-Id: I36c65a8c725c4da76dc70006cd96b0a2b6878e84
-rw-r--r--.gitignore7
-rw-r--r--Makefile.am9
-rw-r--r--Transceiver52M/Makefile.am91
-rw-r--r--Transceiver52M/ms/bladerf_specific.h445
-rw-r--r--Transceiver52M/ms/ipc_specific.h197
-rw-r--r--Transceiver52M/ms/itrq.h213
-rw-r--r--Transceiver52M/ms/l1ctl_server.c275
-rw-r--r--Transceiver52M/ms/l1ctl_server_cb.cpp77
-rw-r--r--Transceiver52M/ms/logging.cpp103
-rw-r--r--Transceiver52M/ms/ms.cpp339
-rw-r--r--Transceiver52M/ms/ms.h264
-rw-r--r--Transceiver52M/ms/ms_rx_burst.h25
-rw-r--r--Transceiver52M/ms/ms_rx_burst_test.cpp207
-rw-r--r--Transceiver52M/ms/ms_rx_lower.cpp343
-rw-r--r--Transceiver52M/ms/ms_upper.cpp451
-rw-r--r--Transceiver52M/ms/ms_upper.h57
-rw-r--r--Transceiver52M/ms/sch.c324
-rw-r--r--Transceiver52M/ms/sch.h48
-rw-r--r--Transceiver52M/ms/uhd_specific.h251
-rw-r--r--configure.ac15
20 files changed, 3730 insertions, 11 deletions
diff --git a/.gitignore b/.gitignore
index 35a1b46..0b33e0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,13 @@ Transceiver52M/osmo-trx-usrp1
Transceiver52M/osmo-trx-lms
Transceiver52M/osmo-trx-ipc
Transceiver52M/osmo-trx-blade
+Transceiver52M/osmo-trx-ipc2
+Transceiver52M/osmo-trx-syncthing-blade
+Transceiver52M/osmo-trx-syncthing-uhd
+Transceiver52M/osmo-trx-syncthing-ipc
+Transceiver52M/osmo-trx-ms-blade
+Transceiver52M/osmo-trx-ms-uhd
+Transceiver52M/osmo-trx-ms-ipc
Transceiver52M/device/ipc/uhddev_ipc.cpp
.clang-format
diff --git a/Makefile.am b/Makefile.am
index 6a3a955..7636b46 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -26,8 +26,15 @@ AM_CXXFLAGS = -Wall -pthread
#AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread
#AM_CFLAGS = -Wall -O2 -NDEBUG -pthread
+SUBDIRS =
+
+if ENABLE_MS_TRX
+ SUBDIRS += osmocom-bb/src/host/trxcon
+endif
+
# Order must be preserved
-SUBDIRS = \
+SUBDIRS += \
+ osmocom-bb/src/host/trxcon \
CommonLibs \
GSM \
Transceiver52M \
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 3f84d3d..5649d15 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -23,9 +23,9 @@ 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_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/
+AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/
noinst_LTLIBRARIES = libtransceiver_common.la
@@ -40,7 +40,9 @@ COMMON_SOURCES = \
ChannelizerBase.cpp \
Channelizer.cpp \
Synthesis.cpp \
- proto_trxd.c
+ proto_trxd.c \
+ grgsm_vitac/grgsm_vitac.cpp \
+ grgsm_vitac/viterbi_detector.cc
libtransceiver_common_la_SOURCES = \
$(COMMON_SOURCES) \
@@ -61,7 +63,9 @@ noinst_HEADERS = \
ChannelizerBase.h \
Channelizer.h \
Synthesis.h \
- proto_trxd.h
+ proto_trxd.h \
+ grgsm_vitac/viterbi_detector.h \
+ grgsm_vitac/constants.h
COMMON_LDADD = \
libtransceiver_common.la \
@@ -70,9 +74,26 @@ COMMON_LDADD = \
$(COMMON_LA) \
$(FFTWF_LIBS) \
$(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCODING_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOVTY_LIBS)
+TRXCON_LDADD = \
+ $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \
+ $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a
+
+MS_SOURCES = \
+ ms/sch.c \
+ ms/ms.cpp \
+ ms/ms_rx_lower.cpp
+
+noinst_HEADERS += \
+ ms/ms.h \
+ ms/bladerf_specific.h \
+ ms/uhd_specific.h \
+ ms/ms_upper.h \
+ ms/itrq.h
+
bin_PROGRAMS =
if DEVICE_UHD
@@ -83,6 +104,26 @@ osmo_trx_uhd_LDADD = \
$(COMMON_LDADD) \
$(UHD_LIBS)
osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS)
+
+if ENABLE_MS_TRX
+bin_PROGRAMS += osmo-trx-ms-uhd
+osmo_trx_ms_uhd_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.cpp ms/l1ctl_server_cb.cpp
+osmo_trx_ms_uhd_LDADD = \
+ $(builddir)/device/uhd/libdevice.la \
+ $(COMMON_LDADD) \
+ $(UHD_LIBS) \
+ $(TRXCON_LDADD)
+osmo_trx_ms_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD
+
+bin_PROGRAMS += osmo-trx-syncthing-uhd
+osmo_trx_syncthing_uhd_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp
+osmo_trx_syncthing_uhd_LDADD = \
+ $(builddir)/device/uhd/libdevice.la \
+ $(COMMON_LDADD) \
+ $(UHD_LIBS) \
+ $(TRXCON_LDADD)
+osmo_trx_syncthing_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DSYNCTHINGONLY -DBUILDUHD
+endif
endif
if DEVICE_USRP1
@@ -114,6 +155,25 @@ osmo_trx_blade_LDADD = \
$(BLADE_LIBS)
osmo_trx_blade_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS)
+if ENABLE_MS_TRX
+bin_PROGRAMS += osmo-trx-ms-blade
+osmo_trx_ms_blade_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.cpp ms/l1ctl_server_cb.cpp
+osmo_trx_ms_blade_LDADD = \
+ $(builddir)/device/bladerf/libdevice.la \
+ $(COMMON_LDADD) \
+ $(BLADE_LIBS) \
+ $(TRXCON_LDADD)
+osmo_trx_ms_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE
+
+bin_PROGRAMS += osmo-trx-syncthing-blade
+osmo_trx_syncthing_blade_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp
+osmo_trx_syncthing_blade_LDADD = \
+ $(builddir)/device/bladerf/libdevice.la \
+ $(COMMON_LDADD) \
+ $(BLADE_LIBS) \
+ $(TRXCON_LDADD)
+osmo_trx_syncthing_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DSYNCTHINGONLY -DBUILDBLADE -I../device/ipc
+endif
endif
if DEVICE_IPC
@@ -123,5 +183,24 @@ osmo_trx_ipc_LDADD = \
$(builddir)/device/ipc/libdevice.la \
$(COMMON_LDADD)
osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
-endif
+# bin_PROGRAMS += osmo-trx-ipc2
+# osmo_trx_ipc2_SOURCES = osmo-trx.cpp
+# osmo_trx_ipc2_LDADD = \
+# $(builddir)/device/ipc2/libdevice.la \
+# $(COMMON_LDADD)
+# osmo_trx_ipc2_CPPFLAGS = $(AM_CPPFLAGS)
+# if ENABLE_MS_TRX
+# bin_PROGRAMS += osmo-trx-ms-ipc
+# osmo_trx_ms_ipc_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp
+# osmo_trx_ms_ipc_LDADD = \
+# $(COMMON_LDADD) \
+# $(TRXCON_LDADD)
+# osmo_trx_ms_ipc_CPPFLAGS = $(AM_CPPFLAGS) -DBUILDIPC -I./device/ipc2 -I../device/ipc2
+
+# bin_PROGRAMS += osmo-trx-syncthing-ipc
+# osmo_trx_syncthing_ipc_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp
+# osmo_trx_syncthing_ipc_LDADD = $(COMMON_LDADD)
+# osmo_trx_syncthing_ipc_CPPFLAGS = $(AM_CPPFLAGS) -DSYNCTHINGONLY -DBUILDIPC -I./device/ipc2 -I../device/ipc2
+# endif
+endif
diff --git a/Transceiver52M/ms/bladerf_specific.h b/Transceiver52M/ms/bladerf_specific.h
new file mode 100644
index 0000000..edfb40f
--- /dev/null
+++ b/Transceiver52M/ms/bladerf_specific.h
@@ -0,0 +1,445 @@
+#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 expand_args(std::ostream &out, Arg &&arg, Args &&...args)
+{
+ out << '(' << std::forward<Arg>(arg);
+ (void)(int[]){ 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;
+ expand_args(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>;
+ const unsigned int rxFullScale, txFullScale;
+ const 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;
+ exit(0);
+ }
+ 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;
+ }
+
+ 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)30);
+ 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;
+ 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;
+ 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);
+ }
+};
diff --git a/Transceiver52M/ms/ipc_specific.h b/Transceiver52M/ms/ipc_specific.h
new file mode 100644
index 0000000..c79e743
--- /dev/null
+++ b/Transceiver52M/ms/ipc_specific.h
@@ -0,0 +1,197 @@
+#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 <cassert>
+#include <complex>
+#include <cstring>
+#include <functional>
+#include <iostream>
+#include <thread>
+
+#include <Timeval.h>
+#include <vector>
+
+// #define MTX_LOG_ENABLED
+#include <ipcif.h>
+
+// typedef unsigned long long TIMESTAMP;
+using blade_sample_type = std::complex<int16_t>;
+const int SAMPLE_SCALE_FACTOR = 1;
+
+struct uhd_buf_wrap {
+ uint64_t ts;
+ uint32_t num_samps;
+ blade_sample_type *buf;
+ auto actual_samples_per_buffer()
+ {
+ return num_samps;
+ }
+ long get_first_ts()
+ {
+ return ts; //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 ipc_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;
+ const unsigned int rxFullScale, txFullScale;
+ const int rxtxdelay;
+ float rxgain, txgain;
+ trxmsif m;
+
+ virtual ~ipc_hw()
+ {
+ delete[] one_pkt_buf;
+ }
+ ipc_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(0)
+ {
+ }
+
+ 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)
+ {
+ return m.connect() ? 0 : -1;
+ }
+
+ void *rx_cb(bh_fn_t burst_handler)
+ {
+ void *ret;
+ static int to_skip = 0;
+ static uint64_t last_ts;
+
+ blade_sample_type pbuf[508 * 2];
+
+ uint64_t t;
+
+ int len = 508 * 2;
+ m.read_dl(508 * 2, &t, pbuf);
+ dev_buf_t rcd = { t, static_cast<uint32_t>(len), pbuf };
+
+ if (to_skip < 120) // prevents weird overflows on startup
+ to_skip++;
+ else {
+ assert(last_ts != rcd.get_first_ts());
+ burst_handler(&rcd);
+ last_ts = rcd.get_first_ts();
+ }
+
+ m.drive_tx();
+
+ return ret;
+ }
+
+ auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [this, burst_handler] {
+ pthread_setname_np(pthread_self(), "rxrun");
+
+ while (1) {
+ rx_cb(burst_handler);
+ }
+ };
+ return fn;
+ }
+ auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
+ {
+ auto fn = [] {
+ // wait_for_shm_open();
+ // dummy
+ };
+ return fn;
+ }
+ void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
+ {
+ // FIXME: missing
+ }
+
+ void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
+ {
+ pthread_setname_np(pthread_self(), name);
+
+ }
+ void signal_start()
+ {
+ m.signal_read_start();
+ }
+};
diff --git a/Transceiver52M/ms/itrq.h b/Transceiver52M/ms/itrq.h
new file mode 100644
index 0000000..62ee848
--- /dev/null
+++ b/Transceiver52M/ms/itrq.h
@@ -0,0 +1,213 @@
+#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 lr, lw;
+ std::atomic_int r_flag, w_flag;
+
+ public:
+ explicit spsc_cond_detail() : r_flag(0), w_flag(0)
+ {
+ }
+
+ ~spsc_cond_detail()
+ {
+ }
+
+ ssize_t spsc_check_r()
+ {
+ std::unique_lock<std::mutex> lk(lr);
+ while (r_flag == 0)
+ cond_r.wait(lk);
+ r_flag--;
+ return 1;
+ }
+ ssize_t spsc_check_w()
+ {
+ std::unique_lock<std::mutex> lk(lw);
+ while (w_flag == 0)
+ cond_w.wait(lk);
+ w_flag--;
+ return 1;
+ }
+ void spsc_notify_r()
+ {
+ std::unique_lock<std::mutex> lk(lr);
+ r_flag++;
+ cond_r.notify_one();
+ }
+ void spsc_notify_w()
+ {
+ std::unique_lock<std::mutex> lk(lw);
+ w_flag++;
+ 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(const ELEM *elem)
+ {
+ size_t cur_wp, cur_rp;
+ cur_wp = writeptr.load(std::memory_order_relaxed);
+ cur_rp = readptr.load(std::memory_order_acquire);
+ if ((cur_wp + 1) % SZ == cur_rp) {
+ if (block_write)
+ base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */
+ return false;
+ }
+ buf[cur_wp] = *elem;
+ writeptr.store((cur_wp + 1) % SZ, std::memory_order_release);
+ if (block_read)
+ base_t::spsc_notify_r(); /* fine after release */
+ return true;
+ }
+
+ /*! Removes element from the queue by copying the data.
+ * \param[in] elem output buffer, must match the originally configured queue buffer size!.
+ * \returns true if queue was not empty and element was successfully removed */
+ bool spsc_pop(ELEM *elem)
+ {
+ size_t cur_wp, cur_rp;
+ cur_wp = writeptr.load(std::memory_order_acquire);
+ cur_rp = readptr.load(std::memory_order_relaxed);
+
+ if (cur_wp == cur_rp) /* blocks via prep_pop */
+ return false;
+
+ *elem = buf[cur_rp];
+ readptr.store((cur_rp + 1) % SZ, std::memory_order_release);
+ if (block_write)
+ base_t::spsc_notify_w();
+ return true;
+ }
+
+ /*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks.
+ * This function can be used to deliberately wait for a non-empty queue on the read side.
+ * \returns result of reading the fd. */
+ ssize_t spsc_prep_pop()
+ {
+ return base_t::spsc_check_r();
+ }
+};
+
+} // namespace spsc_detail
+
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {};
+template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
+class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {}; \ No newline at end of file
diff --git a/Transceiver52M/ms/l1ctl_server.c b/Transceiver52M/ms/l1ctl_server.c
new file mode 100644
index 0000000..9e9083f
--- /dev/null
+++ b/Transceiver52M/ms/l1ctl_server.c
@@ -0,0 +1,275 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * UNIX socket server for L1CTL
+ *
+ * (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
+ * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+ * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+
+#include <osmocom/bb/trxcon/logging.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+
+#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args)
+
+static int l1ctl_client_read_cb(struct osmo_fd *ofd)
+{
+ struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
+ struct msgb *msg;
+ uint16_t len;
+ int rc;
+
+ /* Attempt to read from socket */
+ rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
+ if (rc != L1CTL_MSG_LEN_FIELD) {
+ if (rc <= 0) {
+ LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc,
+ strerror(errno));
+ } else {
+ LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n");
+ rc = -EIO;
+ }
+ l1ctl_client_conn_close(client);
+ return rc;
+ }
+
+ /* Check message length */
+ len = ntohs(len);
+ if (len > L1CTL_LENGTH) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
+ return -EINVAL;
+ }
+
+ /* Allocate a new msg */
+ msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg");
+ if (!msg) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
+ return -ENOMEM;
+ }
+
+ msg->l1h = msgb_put(msg, len);
+ rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc != len) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno));
+ msgb_free(msg);
+ return rc;
+ }
+
+ /* Debug print */
+ LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
+
+ /* Call L1CTL handler */
+ client->server->cfg->conn_read_cb(client, msg);
+
+ return 0;
+}
+
+static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
+ int len;
+
+ if (ofd->fd <= 0)
+ return -EINVAL;
+
+ len = write(ofd->fd, msg->data, msg->len);
+ if (len != msg->len) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len,
+ msg->len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Connection handler */
+static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
+{
+ struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
+ struct l1ctl_client *client;
+ int rc, client_fd;
+
+ client_fd = accept(sfd->fd, NULL, NULL);
+ if (client_fd < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to accept() a new connection: "
+ "%s\n",
+ strerror(errno));
+ return client_fd;
+ }
+
+ if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
+ server->num_clients >= server->cfg->num_clients_max) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "L1CTL server cannot accept more "
+ "than %u connection(s)\n",
+ server->cfg->num_clients_max);
+ close(client_fd);
+ return -ENOMEM;
+ }
+
+ client = talloc_zero(server, struct l1ctl_client);
+ if (client == NULL) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
+ close(client_fd);
+ return -ENOMEM;
+ }
+
+ /* Init the client's write queue */
+ osmo_wqueue_init(&client->wq, 100);
+ INIT_LLIST_HEAD(&client->wq.bfd.list);
+
+ client->wq.write_cb = &l1ctl_client_write_cb;
+ client->wq.read_cb = &l1ctl_client_read_cb;
+ osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
+
+ /* Register the client's write queue */
+ rc = osmo_fd_register(&client->wq.bfd);
+ if (rc != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
+ close(client->wq.bfd.fd);
+ talloc_free(client);
+ return rc;
+ }
+
+ llist_add_tail(&client->list, &server->clients);
+ client->id = server->next_client_id++;
+ client->server = server;
+ server->num_clients++;
+
+ LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
+
+ if (client->server->cfg->conn_accept_cb != NULL)
+ client->server->cfg->conn_accept_cb(client);
+
+ return 0;
+}
+
+int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
+{
+ uint8_t *len;
+
+ /* Debug print */
+ LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
+
+ if (msg->l1h != msg->data)
+ LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
+
+ /* Prepend 16-bit length before sending */
+ len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
+ osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
+
+ if (osmo_wqueue_enqueue(&client->wq, msg) != 0) {
+ LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+void l1ctl_client_conn_close(struct l1ctl_client *client)
+{
+ struct l1ctl_server *server = client->server;
+
+ LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
+
+ if (server->cfg->conn_close_cb != NULL)
+ server->cfg->conn_close_cb(client);
+
+ /* Close connection socket */
+ osmo_fd_unregister(&client->wq.bfd);
+ close(client->wq.bfd.fd);
+ client->wq.bfd.fd = -1;
+
+ /* Clear pending messages */
+ osmo_wqueue_clear(&client->wq);
+
+ client->server->num_clients--;
+ llist_del(&client->list);
+ talloc_free(client);
+
+ /* If this was the last client, reset the client IDs generator to 0.
+ * This way avoid assigning huge unreadable client IDs like 26545. */
+ if (llist_empty(&server->clients))
+ server->next_client_id = 0;
+}
+
+struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
+{
+ struct l1ctl_server *server;
+ int rc;
+
+ LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
+
+ server = talloc(ctx, struct l1ctl_server);
+ OSMO_ASSERT(server != NULL);
+
+ *server = (struct l1ctl_server){
+ .clients = LLIST_HEAD_INIT(server->clients),
+ .cfg = cfg,
+ };
+
+ /* conn_read_cb shall not be NULL */
+ OSMO_ASSERT(cfg->conn_read_cb != NULL);
+
+ /* Bind connection handler */
+ osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
+
+ rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno));
+ talloc_free(server);
+ return NULL;
+ }
+
+ return server;
+}
+
+void l1ctl_server_free(struct l1ctl_server *server)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
+
+ /* Close all client connections */
+ while (!llist_empty(&server->clients)) {
+ struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list);
+ l1ctl_client_conn_close(client);
+ }
+
+ /* Unbind listening socket */
+ if (server->ofd.fd != -1) {
+ osmo_fd_unregister(&server->ofd);
+ close(server->ofd.fd);
+ server->ofd.fd = -1;
+ }
+
+ talloc_free(server);
+}
diff --git a/Transceiver52M/ms/l1ctl_server_cb.cpp b/Transceiver52M/ms/l1ctl_server_cb.cpp
new file mode 100644
index 0000000..5013871
--- /dev/null
+++ b/Transceiver52M/ms/l1ctl_server_cb.cpp
@@ -0,0 +1,77 @@
+/*
+ * (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/>.
+ *
+ */
+
+extern "C" {
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/trxcon/trxcon_fsm.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+}
+
+static struct l1ctl_server_cfg server_cfg;
+static struct l1ctl_server *server = NULL;
+namespace trxcon
+{
+extern struct trxcon_inst *g_trxcon;
+}
+
+static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg)
+{
+ struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
+
+ return trxcon_l1ctl_receive(trxcon, msg);
+}
+
+static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c)
+{
+ l1c->log_prefix = talloc_strdup(l1c, trxcon::g_trxcon->log_prefix);
+ l1c->priv = trxcon::g_trxcon;
+ trxcon::g_trxcon->l2if = l1c;
+}
+
+static void l1ctl_conn_close_cb(struct l1ctl_client *l1c)
+{
+ struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
+
+ if (trxcon == NULL || trxcon->fi == NULL)
+ return;
+
+ osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL);
+}
+
+namespace trxcon
+{
+void trxc_l1ctl_init(void *tallctx)
+{
+ /* Start the L1CTL server */
+ server_cfg = (struct l1ctl_server_cfg){
+ .sock_path = "/tmp/osmocom_l2",
+ .num_clients_max = 1,
+ .conn_read_cb = &l1ctl_rx_cb,
+ .conn_accept_cb = &l1ctl_conn_accept_cb,
+ .conn_close_cb = &l1ctl_conn_close_cb,
+ };
+
+ server = l1ctl_server_alloc(tallctx, &server_cfg);
+ if (server == NULL) {
+ return;
+ }
+}
+} // namespace trxcon \ No newline at end of file
diff --git a/Transceiver52M/ms/logging.cpp b/Transceiver52M/ms/logging.cpp
new file mode 100644
index 0000000..0857c65
--- /dev/null
+++ b/Transceiver52M/ms/logging.cpp
@@ -0,0 +1,103 @@
+/*
+ * (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/>.
+ *
+ */
+
+extern "C" {
+#include <osmocom/core/application.h>
+#include <osmocom/bb/trxcon/logging.h>
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/l1sched/l1sched.h>
+
+}
+static const int trxcon_log_cfg[] = {
+ [TRXCON_LOGC_FSM] = DAPP,
+ [TRXCON_LOGC_L1C] = DL1C,
+ [TRXCON_LOGC_L1D] = DL1D,
+ [TRXCON_LOGC_SCHC] = DSCH,
+ [TRXCON_LOGC_SCHD] = DSCHD,
+};
+
+static struct log_info_cat trxcon_log_info_cat[] = {
+ [DAPP] = {
+ .name = "DAPP",
+ .color = "\033[1;35m",
+ .description = "Application",
+ .loglevel = LOGL_NOTICE, .enabled = 1,
+ },
+ [DL1C] = {
+ .name = "DL1C",
+ .color = "\033[1;31m",
+ .description = "Layer 1 control interface",
+ .loglevel = LOGL_NOTICE, .enabled = 1,
+ },
+ [DL1D] = {
+ .name = "DL1D",
+ .color = "\033[1;31m",
+ .description = "Layer 1 data",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DTRXC] = {
+ .name = "DTRXC",
+ .color = "\033[1;33m",
+ .description = "Transceiver control interface",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DTRXD] = {
+ .name = "DTRXD",
+ .color = "\033[1;33m",
+ .description = "Transceiver data interface",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DSCH] = {
+ .name = "DSCH",
+ .color = "\033[1;36m",
+ .description = "Scheduler management",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 0,
+ },
+ [DSCHD] = {
+ .name = "DSCHD",
+ .color = "\033[1;36m",
+ .description = "Scheduler data",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 0,
+ },
+};
+
+static struct log_info trxcon_log_info = {
+ .cat = trxcon_log_info_cat,
+ .num_cat = ARRAY_SIZE(trxcon_log_info_cat),
+};
+
+namespace trxcon
+{
+
+void trxc_log_init(void *tallctx)
+{
+ osmo_init_logging2(tallctx, &trxcon_log_info);
+ // log_parse_category_mask(osmo_stderr_target, "");
+
+ trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg));
+}
+
+} // namespace trxcon
diff --git a/Transceiver52M/ms/ms.cpp b/Transceiver52M/ms/ms.cpp
new file mode 100644
index 0000000..3a62ed0
--- /dev/null
+++ b/Transceiver52M/ms/ms.cpp
@@ -0,0 +1,339 @@
+
+/*
+ * (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 "ms.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;
+
+#ifdef DBGXX
+const int offsetrange = 200;
+const int offset_start = -15;
+static int offset_ctr = 0;
+#endif
+
+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;
+
+#ifdef DBGXX
+ 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;
+#endif
+
+ 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);
+#ifdef DBGXX
+ auto this_offset = offset_start + (offset_ctr++ % offsetrange);
+ std::cerr << "-- O " << this_offset << std::endl;
+ send_ts = now_ts - timing_advance +
+ ((target.FN() * 8 + (int)target.TN()) - (now_time.FN() * 8 + (int)now_time.TN())) *
+ ONE_TS_BURST_LEN;
+#endif
+ t->submit_burst_ts(buf2, burst->size() + pad, send_ts - pad);
+#ifdef DBGXX
+ signalVector test(burst->size() + pad);
+ convert_and_scale<float, int16_t>(test.begin(), buf2, burst->size() * 2 + pad, 1.f / float(scale));
+ 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;
+#endif
+ }
+}
+#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();
+ 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, trx->rxFullScale).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_dev_and_streams()
+{
+ 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);
+ set_name_aff_sched(rx_task.native_handle(), "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2);
+
+ usleep(1000);
+ auto fn2 = get_tx_burst_handler_fn(tx_bh());
+ tx_task = std::thread(fn2);
+ set_name_aff_sched(tx_task.native_handle(), "txrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
+}
+
+void ms_trx::set_upper_ready(bool is_ready)
+{
+ upper_is_ready = is_ready;
+}
+
+void ms_trx::stop_threads()
+{
+ std::cerr << "killing threads...\r\n" << std::endl;
+ close_device();
+ rx_task.join();
+ tx_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;
+#ifdef DBGXX
+ 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;
+#endif
+#if 1
+ unsigned int pad = 4 * 4;
+ blade_sample_type buf2[len + pad];
+ memset(buf2, 0, pad * sizeof(blade_sample_type));
+ memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type));
+
+ assert(target.FN() == check.FN());
+ assert(target.TN() == check.TN());
+ submit_burst_ts(buf2, len + pad, send_ts - pad);
+#else
+ submit_burst_ts(buffer, len, send_ts);
+#endif
+}
diff --git a/Transceiver52M/ms/ms.h b/Transceiver52M/ms/ms.h
new file mode 100644
index 0000000..8b781a3
--- /dev/null
+++ b/Transceiver52M/ms/ms.h
@@ -0,0 +1,264 @@
+#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>
+#elif defined(BUILDIPC)
+#include "ipc_specific.h"
+#define BASET ipc_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 = 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 {
+ one_burst()
+ {
+ }
+ GSM::Time gsmts;
+ union {
+ blade_sample_type burst[ONE_TS_BURST_LEN];
+ char sch_bits[148];
+ };
+};
+
+using rx_queue_t = spsc_cond<8 * NUM_RXQ_FRAMES, one_burst, true, true>;
+
+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();
+ std::atomic<bool> upper_is_ready;
+ void set_upper_ready(bool is_ready);
+
+ bool handle_sch_or_nb();
+ 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) __attribute__((optnone));
+
+ int init_device();
+ int init_dev_and_streams();
+ 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;
+ }
+
+ void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
+ {
+ set_name_aff_sched(pthread_self(), name, cpunum, schedtype, prio);
+ }
+
+ void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype,
+ int prio)
+ {
+ pthread_setname_np(h, name);
+
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpunum, &cpuset);
+
+ auto rv = pthread_setaffinity_np(h, 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(h, schedtype, &sch_params);
+ if (rv < 0) {
+ std::cerr << name << " sched: errreur! " << std::strerror(errno);
+ return exit(0);
+ }
+ }
+};
diff --git a/Transceiver52M/ms/ms_rx_burst.h b/Transceiver52M/ms/ms_rx_burst.h
new file mode 100644
index 0000000..764a69e
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_burst.h
@@ -0,0 +1,25 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "ms.h"
+
+void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale); \ No newline at end of file
diff --git a/Transceiver52M/ms/ms_rx_burst_test.cpp b/Transceiver52M/ms/ms_rx_burst_test.cpp
new file mode 100644
index 0000000..69fca5e
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_burst_test.cpp
@@ -0,0 +1,207 @@
+/*
+ * (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 "ms.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
+
+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();
+}
+
+static void handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
+{
+ memset(burst.begin(), 0, burst.size() * sizeof(std::complex<float>));
+ const auto is_sch = gsm_sch_check_ts(e.gsmts.TN(), e.gsmts.FN());
+ const auto is_fcch = gsm_fcch_check_ts(e.gsmts.TN(), e.gsmts.FN());
+
+ if (is_fcch)
+ return;
+
+ if (is_sch) {
+ 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 / float(scale));
+
+ 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 / float(scale));
+ // std::cerr << "@" << tsc << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << " "
+ // << std::endl;
+
+ 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);
+ ;
+#ifdef DBGXX
+ // auto bits = SoftVector(148);
+ // for (int i = 0; i < 148; i++)
+ // (bits)[i] = outbin[i] < 1 ? -1 : 1;
+#endif
+#endif
+}
+
+void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale)
+{
+ 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, scale);
+#ifdef DBGXX
+ 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();
+#endif
+ }
+} \ No newline at end of file
diff --git a/Transceiver52M/ms/ms_rx_lower.cpp b/Transceiver52M/ms/ms_rx_lower.cpp
new file mode 100644
index 0000000..780ab42
--- /dev/null
+++ b/Transceiver52M/ms/ms_rx_lower.cpp
@@ -0,0 +1,343 @@
+/*
+ * (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 "ms.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
+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], 1, 39);
+ float_to_sbit(&bits[106], &data[39], 1, 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
+
+ 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;
+}
+
+static char sch_demod_bits[148];
+
+bool ms_trx::handle_sch_or_nb()
+{
+ one_burst brst;
+ const auto current_gsm_time = timekeeper.gsmtime();
+ const auto is_sch = gsm_sch_check_ts(current_gsm_time.TN(), current_gsm_time.FN());
+ const auto is_fcch = gsm_fcch_check_ts(current_gsm_time.TN(), current_gsm_time.FN());
+#pragma unused(is_fcch)
+
+ //either pass burst to upper layer for demod, OR pass demodded SCH to upper layer so we don't waste time processing it twice
+ brst.gsmts = current_gsm_time;
+
+ if (!is_sch) {
+ memcpy(brst.burst, burst_copy_buffer, sizeof(blade_sample_type) * ONE_TS_BURST_LEN);
+ } else {
+ handle_sch(false);
+ memcpy(brst.sch_bits, sch_demod_bits, sizeof(sch_demod_bits));
+ }
+#ifndef SYNCTHINGONLY
+ if (upper_is_ready) { // this is blocking, so only submit if there is a reader - only if upper exists!
+#endif
+ while (!rxqueue.spsc_push(&brst))
+ ;
+#ifndef SYNCTHINGONLY
+ }
+#endif
+
+ if (do_auto_gain)
+ maybe_update_gain(brst);
+
+ return false;
+}
+
+static float sch_acq_buffer[SCH_LEN_SPS * 2];
+
+bool ms_trx::handle_sch(bool is_first_sch_acq)
+{
+ auto current_gsm_time = timekeeper.gsmtime();
+ const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN;
+ const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer;
+ const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2];
+ const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer);
+ std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+
+ int start;
+ memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
+ if (is_first_sch_acq) {
+ float max_corr = 0;
+ convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2,
+ 1.f / float(rxFullScale));
+ start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr);
+ detect_burst(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
+ } else {
+ convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2,
+ 1.f / float(rxFullScale));
+ start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]);
+ start = start < 39 ? start : 39;
+ start = start > -39 ? start : -39;
+ detect_burst(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
+ }
+
+ SoftVector bitss(148);
+ for (int i = 0; i < 148; i++) {
+ bitss[i] = (sch_demod_bits[i]);
+ }
+
+ 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) - 1;
+ } 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;
+}
+
+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;
+ auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining);
+ if (rd != first_remaining) {
+ partial_rdofs += rd;
+ return;
+ }
+
+ timekeeper.inc_and_update_safe(rcd->get_first_ts() - partial_rdofs);
+ handle_sch_or_nb();
+ to_skip = first_remaining;
+ }
+
+ // apply sample rate slippage compensation
+ to_skip -= temp_ts_corr_offset;
+
+ // FIXME: happens rarely, read_n start -1 blows up
+ // this is fine: will just be corrected one buffer later
+ if (to_skip < 0)
+ to_skip = 0;
+ else
+ temp_ts_corr_offset = 0;
+
+ const auto left_after_burst = rcd->actual_samples_per_buffer() - to_skip;
+
+ const int full = left_after_burst / ONE_TS_BURST_LEN;
+ const int frac = left_after_burst % ONE_TS_BURST_LEN;
+
+ for (int i = 0; i < full; i++) {
+ rcd->read_n(burst_copy_buffer, to_skip + i * ONE_TS_BURST_LEN, ONE_TS_BURST_LEN);
+ timekeeper.inc_and_update_safe(rcd->get_first_ts() + to_skip + i * ONE_TS_BURST_LEN);
+ handle_sch_or_nb();
+ }
+
+ if (frac)
+ rcd->read_n(burst_copy_buffer, to_skip + full * ONE_TS_BURST_LEN, frac);
+ partial_rdofs = frac;
+}
diff --git a/Transceiver52M/ms/ms_upper.cpp b/Transceiver52M/ms/ms_upper.cpp
new file mode 100644
index 0000000..dd25fd2
--- /dev/null
+++ b/Transceiver52M/ms/ms_upper.cpp
@@ -0,0 +1,451 @@
+/*
+ * (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 "ms.h"
+#include <signalVector.h>
+#include <radioVector.h>
+#include <radioInterface.h>
+#include "grgsm_vitac/grgsm_vitac.h"
+
+extern "C" {
+#include <osmocom/core/select.h>
+#include "sch.h"
+#include "convolve.h"
+#include "convert.h"
+#include "proto_trxd.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <time.h>
+
+#ifdef LSANDEBUG
+void __lsan_do_recoverable_leak_check();
+#endif
+}
+
+#include "ms_upper.h"
+
+namespace trxcon
+{
+extern "C" {
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+
+// #include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/bb/trxcon/logging.h>
+
+#include <osmocom/bb/trxcon/trxcon.h>
+#include <osmocom/bb/trxcon/trxcon_fsm.h>
+#include <osmocom/bb/trxcon/phyif.h>
+#include <osmocom/bb/trxcon/trx_if.h>
+#include <osmocom/bb/trxcon/l1ctl_server.h>
+
+#include <osmocom/bb/l1sched/l1sched.h>
+// #include <osmocom/bb/l1sched/logging.h>
+}
+struct trxcon_inst *g_trxcon;
+// trx_instance *trxcon_instance; // local handle
+struct internal_q_tx_buf {
+ trxcon_phyif_burst_req r;
+ uint8_t buf[148];
+};
+using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>;
+using cmd_queue_t = spsc_cond<8 * 1, trxcon_phyif_cmd, true, false>;
+using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>;
+static tx_queue_t txq;
+static cmd_queue_t cmdq_to_phy;
+static cmdr_queue_t cmdq_from_phy;
+
+extern void trxc_log_init(void *tallctx);
+extern void trxc_l1ctl_init(void *tallctx);
+
+} // namespace trxcon
+
+#ifdef LOG
+#undef LOG
+#define LOG(...) upper_trx::dummy_log()
+#endif
+
+#define DBGLG(...) upper_trx::dummy_log()
+
+void upper_trx::start_threads()
+{
+ thr_control = std::thread([this] {
+ set_name_aff_sched("upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR));
+ while (1) {
+ driveControl();
+ }
+ });
+ 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();
+ }
+ });
+
+ // atomic ensures data is not written to q until loop reads
+ start_lower_ms();
+
+ set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5);
+ while (1) {
+ // set_upper_ready(true);
+ driveReceiveFIFO();
+ osmo_select_main(1);
+
+ trxcon::trxcon_phyif_rsp r;
+ if (trxcon::cmdq_from_phy.spsc_pop(&r)) {
+ DBGLG() << "HAVE RESP:" << r.type << std::endl;
+ trxcon_phyif_handle_rsp(trxcon::g_trxcon, &r);
+ }
+ }
+
+#ifdef LSANDEBUG
+ std::thread([this] {
+ set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10);
+
+ while (1) {
+ std::this_thread::sleep_for(std::chrono::seconds{ 5 });
+ __lsan_do_recoverable_leak_check();
+ }
+ }).detach();
+#endif
+}
+
+void upper_trx::start_lower_ms()
+{
+ ms_trx::start();
+}
+
+bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset)
+{
+ float pow, avg = 1.0;
+ static complex workbuf[40 + 625 + 40];
+ static signalVector sv(workbuf, 40, 625);
+ one_burst e;
+ auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[40]);
+ memset((void *)&workbuf[0], 0, sizeof(workbuf));
+ // assert(sv.begin() == &workbuf[40]);
+
+ while (!rxqueue.spsc_pop(&e)) {
+ rxqueue.spsc_prep_pop();
+ }
+
+ wTime = e.gsmts;
+
+ const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN());
+ const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN());
+
+ trxcon::trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) };
+ trxcon::trxcon_phyif_rtr_rsp r = {};
+ trxcon_phyif_handle_rtr_ind(trxcon::g_trxcon, &i, &r);
+ if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE))
+ return false;
+
+ if (is_fcch) {
+ // return trash
+ return true;
+ }
+
+ if (is_sch) {
+ for (int i = 0; i < 148; i++)
+ (demodded_softbits)[i] = (e.sch_bits[i]);
+ RSSI = 10;
+ timingOffset = 0;
+ return true;
+ }
+
+ convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
+
+ pow = energyDetect(sv, 20 * 4 /*sps*/);
+ if (pow < -1) {
+ LOG(ALERT) << "Received empty burst";
+ return false;
+ }
+
+ avg = sqrt(pow);
+ {
+ float ncmax;
+ std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
+ auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
+#ifdef DBGXX
+ float dcmax;
+ std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
+ 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;
+#endif
+ normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39;
+ normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39;
+#ifdef DBGXX
+ // fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN());
+ // if (is_nb)
+#endif
+ detect_burst(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits);
+#ifdef DBGXX
+ // else
+ // detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
+#endif
+ }
+ RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
+ timingOffset = (int)round(0);
+
+ return true;
+}
+
+void upper_trx::driveReceiveFIFO()
+{
+ int RSSI;
+ int TOA; // in 1/256 of a symbol
+ GSM::Time burstTime;
+
+ if (!mOn)
+ return;
+
+ if (pullRadioVector(burstTime, RSSI, TOA)) {
+ // trxcon::trx_data_rx_handler(trxcon::trxcon_instance, (uint8_t *)&response);
+ trxcon::trxcon_phyif_burst_ind bi;
+ bi.fn = burstTime.FN();
+ bi.tn = burstTime.TN();
+ bi.rssi = RSSI;
+ bi.toa256 = TOA;
+ bi.burst = (sbit_t *)demodded_softbits;
+ bi.burst_len = sizeof(demodded_softbits);
+ // trxcon_phyif_handle_clock_ind(trxcon::g_trxcon, bi.fn);
+ trxcon_phyif_handle_burst_ind(trxcon::g_trxcon, &bi);
+ }
+
+ struct trxcon::trxcon_phyif_rts_ind rts {
+ static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN())
+ };
+ trxcon_phyif_handle_rts_ind(trxcon::g_trxcon, &rts);
+}
+
+void upper_trx::driveTx()
+{
+ trxcon::internal_q_tx_buf e;
+ while (!trxcon::txq.spsc_pop(&e)) {
+ trxcon::txq.spsc_prep_pop();
+ }
+
+ trxcon::internal_q_tx_buf *burst = &e;
+
+#ifdef TXDEBUG
+ DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN()
+ << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl;
+#endif
+
+ auto currTime = GSM::Time(burst->r.fn, burst->r.tn);
+ int RSSI = (int)burst->r.pwr;
+
+ static BitVector newBurst(gSlotLen);
+ BitVector::iterator itr = newBurst.begin();
+ auto *bufferItr = burst->buf;
+ while (itr < newBurst.end())
+ *itr++ = *bufferItr++;
+
+ auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
+ scaleVector(*txburst, txFullScale * 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);
+#ifdef TXDEBUG
+ 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)
+ DBGLG() << "RACH D! " << ebp.toa << std::endl;
+ else
+ DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
+
+ // memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
+#endif
+ submit_burst(burst_buf, txburst->size(), currTime);
+ delete txburst;
+}
+
+static const char *cmd2str(trxcon::trxcon_phyif_cmd_type c)
+{
+ switch (c) {
+ case trxcon::TRXCON_PHYIF_CMDT_RESET:
+ return "TRXCON_PHYIF_CMDT_RESET";
+ case trxcon::TRXCON_PHYIF_CMDT_POWERON:
+ return "TRXCON_PHYIF_CMDT_POWERON";
+ case trxcon::TRXCON_PHYIF_CMDT_POWEROFF:
+ return "TRXCON_PHYIF_CMDT_POWEROFF";
+ case trxcon::TRXCON_PHYIF_CMDT_MEASURE:
+ return "TRXCON_PHYIF_CMDT_MEASURE";
+ case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0:
+ return "TRXCON_PHYIF_CMDT_SETFREQ_H0";
+ case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1:
+ return "TRXCON_PHYIF_CMDT_SETFREQ_H1";
+ case trxcon::TRXCON_PHYIF_CMDT_SETSLOT:
+ return "TRXCON_PHYIF_CMDT_SETSLOT";
+ case trxcon::TRXCON_PHYIF_CMDT_SETTA:
+ return "TRXCON_PHYIF_CMDT_SETTA";
+ default:
+ return "UNKNOWN COMMAND!";
+ }
+}
+
+static void print_cmd(trxcon::trxcon_phyif_cmd_type c)
+{
+ DBGLG() << cmd2str(c) << std::endl;
+}
+
+bool upper_trx::driveControl()
+{
+ trxcon::trxcon_phyif_rsp r;
+ trxcon::trxcon_phyif_cmd cmd;
+ while (!trxcon::cmdq_to_phy.spsc_pop(&cmd)) {
+ trxcon::cmdq_to_phy.spsc_prep_pop();
+ }
+ print_cmd(cmd.type);
+
+ switch (cmd.type) {
+ case trxcon::TRXCON_PHYIF_CMDT_RESET:
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_POWERON:
+
+ if (!mOn) {
+ // start_ms();
+ set_upper_ready(true);
+ mOn = true;
+ }
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_POWEROFF:
+ // set_upper_ready(false);
+ set_ta(0);
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_MEASURE:
+ r.type = trxcon::trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE;
+ r.param.measure.band_arfcn = cmd.param.measure.band_arfcn;
+ r.param.measure.dbm = -80;
+ tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100);
+ tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100);
+ trxcon::cmdq_from_phy.spsc_push(&r);
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0:
+ // gsm_arfcn2band_rc(uint16_t arfcn, enum gsm_band *band)
+ tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100);
+ tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100);
+
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1:
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_SETSLOT:
+ break;
+ case trxcon::TRXCON_PHYIF_CMDT_SETTA:
+ set_ta(cmd.param.setta.ta);
+ break;
+ }
+ return false;
+}
+
+// trxcon C call(back) if
+extern "C" {
+int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon::trxcon_phyif_burst_req *br)
+{
+ if (br->burst_len == 0) // dummy/nope
+ return 0;
+ assert(br->burst != 0);
+
+ trxcon::internal_q_tx_buf b;
+ b.r = *br;
+ memcpy(b.buf, (void *)br->burst, br->burst_len);
+ trxcon::txq.spsc_push(&b);
+ return 0;
+}
+
+int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon::trxcon_phyif_cmd *cmd)
+{
+ DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl;
+ trxcon::cmdq_to_phy.spsc_push(cmd);
+ // q for resp polling happens in main loop
+ return 0;
+}
+
+void trxcon_phyif_close(void *phyif)
+{
+}
+
+void trxcon_l1ctl_close(struct trxcon::trxcon_inst *trxcon)
+{
+ /* Avoid use-after-free: both *fi and *trxcon are children of
+ * the L2IF (L1CTL connection), so we need to re-parent *fi
+ * to NULL before calling l1ctl_client_conn_close(). */
+ talloc_steal(NULL, trxcon->fi);
+ trxcon::l1ctl_client_conn_close((struct trxcon::l1ctl_client *)trxcon->l2if);
+}
+
+int trxcon_l1ctl_send(struct trxcon::trxcon_inst *trxcon, struct trxcon::msgb *msg)
+{
+ struct trxcon::l1ctl_client *l1c = (struct trxcon::l1ctl_client *)trxcon->l2if;
+
+ return trxcon::l1ctl_client_send(l1c, msg);
+}
+}
+
+int main(int argc, char *argv[])
+{
+ auto tall_trxcon_ctx = talloc_init("trxcon context");
+ trxcon::msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
+ trxcon::trxc_log_init(tall_trxcon_ctx);
+
+ trxcon::g_trxcon = trxcon::trxcon_inst_alloc(tall_trxcon_ctx, 0, 3);
+ trxcon::g_trxcon->gsmtap = 0;
+ trxcon::g_trxcon->phyif = (void *)0x1234;
+
+ 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();
+ trx->set_name_aff_sched("main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5);
+
+ trxcon::trxc_l1ctl_init(tall_trxcon_ctx);
+
+ trx->start_threads();
+
+ return status;
+}
diff --git a/Transceiver52M/ms/ms_upper.h b/Transceiver52M/ms/ms_upper.h
new file mode 100644
index 0000000..a8a86a4
--- /dev/null
+++ b/Transceiver52M/ms/ms_upper.h
@@ -0,0 +1,57 @@
+
+#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 "ms.h"
+
+namespace trxcon
+{
+extern "C" {
+#include <osmocom/bb/trxcon/phyif.h>
+#include <osmocom/bb/trxcon/trx_if.h>
+}
+} // namespace trxcon
+class upper_trx : public ms_trx {
+ bool mOn;
+ char demodded_softbits[444];
+
+ // void driveControl();
+ bool driveControl();
+ void driveReceiveFIFO();
+ void driveTx();
+
+ bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) __attribute__((optnone));
+
+ std::thread thr_control, thr_rx, thr_tx;
+
+ public:
+ void start_threads();
+ void start_lower_ms();
+
+ upper_trx(){};
+};
diff --git a/Transceiver52M/ms/sch.c b/Transceiver52M/ms/sch.c
new file mode 100644
index 0000000..d05c4e2
--- /dev/null
+++ b/Transceiver52M/ms/sch.c
@@ -0,0 +1,324 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2016 by Tom Tsou <tom.tsou@ettus.com>
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ */
+
+#include <complex.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#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"
+
+/* GSM 04.08, 9.1.30 Synchronization channel information */
+struct sch_packed_info {
+ ubit_t t1_hi[2];
+ ubit_t bsic[6];
+ ubit_t t1_md[8];
+ ubit_t t3p_hi[2];
+ ubit_t t2[5];
+ ubit_t t1_lo[1];
+ ubit_t t3p_lo[1];
+} __attribute__((packed));
+
+struct sch_burst {
+ sbit_t tail0[3];
+ sbit_t data0[39];
+ sbit_t etsc[64];
+ sbit_t data1[39];
+ sbit_t tail1[3];
+ sbit_t guard[8];
+} __attribute__((packed));
+
+static const uint8_t sch_next_output[][2] = {
+ { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
+ { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
+ { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
+ { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
+};
+
+static const uint8_t sch_next_state[][2] = {
+ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 },
+ { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 },
+ { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+};
+
+static const struct osmo_conv_code gsm_conv_sch = {
+ .N = 2,
+ .K = 5,
+ .len = GSM_SCH_UNCODED_LEN,
+ .next_output = sch_next_output,
+ .next_state = sch_next_state,
+};
+
+#define GSM_MAX_BURST_LEN 157 * 4
+#define GSM_SYM_RATE (1625e3 / 6) * 4
+
+/* Pre-generated FCCH measurement tone */
+static complex float fcch_ref[GSM_MAX_BURST_LEN];
+
+int float_to_sbit(const float *in, sbit_t *out, float scale, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ out[i] = (in[i] - 0.5f) * scale;
+ }
+
+ return 0;
+}
+
+/* Check if FN contains a FCCH burst */
+int gsm_fcch_check_fn(int fn)
+{
+ int fn51 = fn % 51;
+
+ switch (fn51) {
+ case 0:
+ case 10:
+ case 20:
+ case 30:
+ case 40:
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Check if FN contains a SCH burst */
+int gsm_sch_check_fn(int fn)
+{
+ int fn51 = fn % 51;
+
+ switch (fn51) {
+ case 1:
+ case 11:
+ case 21:
+ case 31:
+ case 41:
+ return 1;
+ }
+
+ return 0;
+}
+
+int gsm_fcch_check_ts(int ts, int fn) {
+ return ts == 0 && gsm_fcch_check_fn(fn);
+}
+
+int gsm_sch_check_ts(int ts, int fn) {
+ return ts == 0 && gsm_sch_check_fn(fn);
+}
+
+/* SCH (T1, T2, T3p) to full FN value */
+int gsm_sch_to_fn(struct sch_info *sch)
+{
+ int t1 = sch->t1;
+ int t2 = sch->t2;
+ int t3p = sch->t3p;
+
+ if ((t1 < 0) || (t2 < 0) || (t3p < 0))
+ return -1;
+ int tt;
+ int t3 = t3p * 10 + 1;
+
+ if (t3 < t2)
+ tt = (t3 + 26) - t2;
+ else
+ tt = (t3 - t2) % 26;
+
+ return t1 * 51 * 26 + tt * 51 + t3;
+}
+
+/* Parse encoded SCH message */
+int gsm_sch_parse(const uint8_t *info, struct sch_info *desc)
+{
+ struct sch_packed_info *p = (struct sch_packed_info *) info;
+
+ desc->bsic = (p->bsic[0] << 0) | (p->bsic[1] << 1) |
+ (p->bsic[2] << 2) | (p->bsic[3] << 3) |
+ (p->bsic[4] << 4) | (p->bsic[5] << 5);
+
+ desc->t1 = (p->t1_lo[0] << 0) | (p->t1_md[0] << 1) |
+ (p->t1_md[1] << 2) | (p->t1_md[2] << 3) |
+ (p->t1_md[3] << 4) | (p->t1_md[4] << 5) |
+ (p->t1_md[5] << 6) | (p->t1_md[6] << 7) |
+ (p->t1_md[7] << 8) | (p->t1_hi[0] << 9) |
+ (p->t1_hi[1] << 10);
+
+ desc->t2 = (p->t2[0] << 0) | (p->t2[1] << 1) |
+ (p->t2[2] << 2) | (p->t2[3] << 3) |
+ (p->t2[4] << 4);
+
+ desc->t3p = (p->t3p_lo[0] << 0) | (p->t3p_hi[0] << 1) |
+ (p->t3p_hi[1] << 2);
+
+ return 0;
+}
+
+/* From osmo-bts */
+int gsm_sch_decode(uint8_t *info, sbit_t *data)
+{
+ int rc;
+ ubit_t uncoded[GSM_SCH_UNCODED_LEN];
+
+ osmo_conv_decode(&gsm_conv_sch, data, uncoded);
+
+ rc = osmo_crc16gen_check_bits(&gsm0503_sch_crc10,
+ uncoded, GSM_SCH_INFO_LEN,
+ uncoded + GSM_SCH_INFO_LEN);
+ if (rc)
+ return -1;
+
+ memcpy(info, uncoded, GSM_SCH_INFO_LEN * sizeof(ubit_t));
+
+ return 0;
+}
+
+#define FCCH_TAIL_BITS_LEN 3*4
+#define FCCH_DATA_LEN 100*4// 142
+#if 1
+/* Compute FCCH frequency offset */
+double org_gsm_fcch_offset(float *burst, int len)
+{
+ int i, start, end;
+ float a, b, c, d, ang, avg = 0.0f;
+ double freq;
+
+ if (len > GSM_MAX_BURST_LEN)
+ len = GSM_MAX_BURST_LEN;
+
+ for (i = 0; i < len; i++) {
+ a = burst[2 * i + 0];
+ b = burst[2 * i + 1];
+ c = crealf(fcch_ref[i]);
+ d = cimagf(fcch_ref[i]);
+
+ burst[2 * i + 0] = a * c - b * d;
+ burst[2 * i + 1] = a * d + b * c;
+ }
+
+ start = FCCH_TAIL_BITS_LEN;
+ end = start + FCCH_DATA_LEN;
+
+ for (i = start; i < end; i++) {
+ a = cargf(burst[2 * (i - 1) + 0] +
+ burst[2 * (i - 1) + 1] * I);
+ b = cargf(burst[2 * i + 0] +
+ burst[2 * i + 1] * I);
+
+ ang = b - a;
+
+ if (ang > M_PI)
+ ang -= 2 * M_PI;
+ else if (ang < -M_PI)
+ ang += 2 * M_PI;
+
+ avg += ang;
+ }
+
+ avg /= (float) (end - start);
+ freq = avg / (2 * M_PI) * GSM_SYM_RATE;
+
+ return freq;
+}
+
+
+static const int L1 = 3;
+static const int L2 = 32;
+static const int N1 = 92;
+static const int N2 = 92;
+
+static struct { int8_t r; int8_t s; } P_inv_table[3+32];
+
+void pinv(int P, int8_t* r, int8_t* s, int L1, int L2) {
+ for (int i = 0; i < L1; i++)
+ for (int j = 0; j < L2; j++)
+ if (P == L2 * i - L1 * j) {
+ *r = i;
+ *s = j;
+ return;
+ }
+}
+
+
+float ac_sum_with_lag( complex float* in, int lag, int offset, int N) {
+ complex float v = 0 + 0*I;
+ int total_offset = offset + lag;
+ for (int s = 0; s < N; s++)
+ v += in[s + total_offset] * conjf(in[s + total_offset - lag]);
+ return cargf(v);
+}
+
+
+double gsm_fcch_offset(float *burst, int len)
+{
+ int start;
+
+ const float fs = 13. / 48. * 1e6 * 4;
+ const float expected_fcch_val = ((2 * M_PI) / (fs)) * 67700;
+
+ if (len > GSM_MAX_BURST_LEN)
+ len = GSM_MAX_BURST_LEN;
+
+ start = FCCH_TAIL_BITS_LEN+10 * 4;
+ float alpha_one = ac_sum_with_lag((complex float*)burst, L1, start, N1);
+ float alpha_two = ac_sum_with_lag((complex float*)burst, L2, start, N2);
+
+ float P_unrounded = (L1 * alpha_two - L2 * alpha_one) / (2 * M_PI);
+ int P = roundf(P_unrounded);
+
+ int8_t r = 0, s = 0;
+ pinv(P, &r, &s, L1, L2);
+
+ float omegal1 = (alpha_one + 2 * M_PI * r) / L1;
+ float omegal2 = (alpha_two + 2 * M_PI * s) / L2;
+
+ float rv = org_gsm_fcch_offset(burst, len);
+ //return rv;
+
+ float reval = GSM_SYM_RATE / (2 * M_PI) * (expected_fcch_val - (omegal1+omegal2)/2);
+ //fprintf(stderr, "XX rv %f %f %f %f\n", rv, reval, omegal1 / (2 * M_PI) * fs, omegal2 / (2 * M_PI) * fs);
+
+ //fprintf(stderr, "XX rv %f %f\n", rv, reval);
+
+ return -reval;
+}
+#endif
+/* Generate FCCH measurement tone */
+static __attribute__((constructor)) void init()
+{
+ int i;
+ double freq = 0.25;
+
+ for (i = 0; i < GSM_MAX_BURST_LEN; i++) {
+ fcch_ref[i] = sin(2 * M_PI * freq * (double) i) +
+ cos(2 * M_PI * freq * (double) i) * I;
+ }
+
+}
diff --git a/Transceiver52M/ms/sch.h b/Transceiver52M/ms/sch.h
new file mode 100644
index 0000000..1af2114
--- /dev/null
+++ b/Transceiver52M/ms/sch.h
@@ -0,0 +1,48 @@
+#pragma once
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2016 by Tom Tsou <tom.tsou@ettus.com>
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ */
+
+#include <osmocom/core/bits.h>
+
+struct sch_info {
+ int bsic;
+ int t1;
+ int t2;
+ int t3p;
+};
+
+#define GSM_SCH_INFO_LEN 25
+#define GSM_SCH_UNCODED_LEN 35
+#define GSM_SCH_CODED_LEN 78
+
+int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst);
+int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc);
+int gsm_sch_to_fn(struct sch_info *sch);
+int gsm_sch_check_fn(int fn);
+int gsm_fcch_check_fn(int fn);
+int gsm_fcch_check_ts(int ts, int fn);
+int gsm_sch_check_ts(int ts, int fn);
+
+double gsm_fcch_offset(float *burst, int len);
+
+int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
+
diff --git a/Transceiver52M/ms/uhd_specific.h b/Transceiver52M/ms/uhd_specific.h
new file mode 100644
index 0000000..ed1a72a
--- /dev/null
+++ b/Transceiver52M/ms/uhd_specific.h
@@ -0,0 +1,251 @@
+#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;
+ const unsigned int rxFullScale, txFullScale;
+ const int rxtxdelay;
+ float rxgain, txgain;
+ volatile bool stop_me_flag;
+
+ virtual ~uhd_hw()
+ {
+ delete[] one_pkt_buf;
+ }
+ uhd_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67), stop_me_flag(false)
+ {
+ }
+
+ void close_device()
+ {
+ stop_me_flag = true;
+ }
+
+ 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, 1.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\n") << std::endl;
+ 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 (!stop_me_flag) {
+ 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, 1.0);
+#ifdef DBGXX
+ 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;
+#endif
+ }
+}; \ No newline at end of file
diff --git a/configure.ac b/configure.ac
index 020f7e3..6ce9ad4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -327,6 +327,14 @@ AC_MSG_RESULT([CFLAGS="$CFLAGS"])
AC_MSG_RESULT([CXXFLAGS="$CXXFLAGS"])
AC_MSG_RESULT([LDFLAGS="$LDFLAGS"])
+AM_CONDITIONAL(ENABLE_MS_TRX, [test -d osmocom-bb])
+
+if ENABLE_MS_TRX; then
+ AC_MSG_NOTICE(["Enabling ms-trx..."])
+ AC_CONFIG_SUBDIRS([osmocom-bb/src/host/trxcon])
+fi
+
+
dnl Output files
AC_CONFIG_FILES([\
Makefile \
@@ -353,8 +361,7 @@ AC_CONFIG_FILES([\
doc/examples/Makefile \
contrib/Makefile \
contrib/systemd/Makefile \
+ doc/manuals/Makefile \
+ contrib/osmo-trx.spec \
])
-
-AC_OUTPUT(
- doc/manuals/Makefile
- contrib/osmo-trx.spec)
+AC_OUTPUT