From a0e1ed3215a142ded2a8a9c51c88e310885d6429 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Nov 2022 19:21:08 +0100 Subject: [PATCH] ms-trx support 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 --- .gitignore | 7 + Makefile.am | 9 +- Transceiver52M/Makefile.am | 91 ++++- Transceiver52M/ms/bladerf_specific.h | 445 ++++++++++++++++++++++++ Transceiver52M/ms/ipc_specific.h | 197 +++++++++++ Transceiver52M/ms/itrq.h | 213 ++++++++++++ Transceiver52M/ms/l1ctl_server.c | 275 +++++++++++++++ Transceiver52M/ms/l1ctl_server_cb.cpp | 77 +++++ Transceiver52M/ms/logging.cpp | 103 ++++++ Transceiver52M/ms/ms.cpp | 339 +++++++++++++++++++ Transceiver52M/ms/ms.h | 264 +++++++++++++++ Transceiver52M/ms/ms_rx_burst.h | 25 ++ Transceiver52M/ms/ms_rx_burst_test.cpp | 207 ++++++++++++ Transceiver52M/ms/ms_rx_lower.cpp | 343 +++++++++++++++++++ Transceiver52M/ms/ms_upper.cpp | 451 +++++++++++++++++++++++++ Transceiver52M/ms/ms_upper.h | 57 ++++ Transceiver52M/ms/sch.c | 324 ++++++++++++++++++ Transceiver52M/ms/sch.h | 48 +++ Transceiver52M/ms/uhd_specific.h | 251 ++++++++++++++ configure.ac | 15 +- 20 files changed, 3730 insertions(+), 11 deletions(-) create mode 100644 Transceiver52M/ms/bladerf_specific.h create mode 100644 Transceiver52M/ms/ipc_specific.h create mode 100644 Transceiver52M/ms/itrq.h create mode 100644 Transceiver52M/ms/l1ctl_server.c create mode 100644 Transceiver52M/ms/l1ctl_server_cb.cpp create mode 100644 Transceiver52M/ms/logging.cpp create mode 100644 Transceiver52M/ms/ms.cpp create mode 100644 Transceiver52M/ms/ms.h create mode 100644 Transceiver52M/ms/ms_rx_burst.h create mode 100644 Transceiver52M/ms/ms_rx_burst_test.cpp create mode 100644 Transceiver52M/ms/ms_rx_lower.cpp create mode 100644 Transceiver52M/ms/ms_upper.cpp create mode 100644 Transceiver52M/ms/ms_upper.h create mode 100644 Transceiver52M/ms/sch.c create mode 100644 Transceiver52M/ms/sch.h create mode 100644 Transceiver52M/ms/uhd_specific.h diff --git a/.gitignore b/.gitignore index 35a1b462..0b33e0fe 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 6a3a955f..7636b464 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 3f84d3dd..5649d159 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 00000000..edfb40f2 --- /dev/null +++ b/Transceiver52M/ms/bladerf_specific.h @@ -0,0 +1,445 @@ +#pragma once + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include "itrq.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 void expand_args(std::ostream &out, Arg &&arg, Args &&...args) +{ + out << '(' << std::forward(arg); + (void)(int[]){ 0, (void((out << "," << std::forward(args))), 0)... }; + out << ')' << std::endl; +} + +template using RvalFunc = R (*)(Args...); + +// specialisation for funcs which return a value +template +R exec_and_check(RvalFunc func, const char *fname, const char *finame, const char *funcname, int line, + Args... args) +{ + R rval = func(std::forward(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; +enum class blade_speed_buffer_type { HS, SS }; +template 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) == 2048, "blade buffer mismatch!"); +static_assert(sizeof(blade_usb_message) == 1024, "blade buffer mismatch!"); +template struct blade_otw_buffer { + static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!"); + blade_usb_message m[SZ]; + int actual_samples_per_msg() + { + return sizeof(blade_usb_message::d) / sizeof(typeof(blade_usb_message::d[0])); + } + int actual_samples_per_buffer() + { + return SZ * actual_samples_per_msg(); + } + int samples_per_buffer() + { + return SZ * sizeof(blade_usb_message) / sizeof(typeof(blade_usb_message::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 struct blade_otw_buffer_helper { + static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!"); + static blade_otw_buffer x; +}; + +using dev_buf_t = typeof(blade_otw_buffer_helper::x); +// using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>; +using bh_fn_t = std::function; + +template 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; + 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((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(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(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 00000000..c79e743a --- /dev/null +++ b/Transceiver52M/ms/ipc_specific.h @@ -0,0 +1,197 @@ +#pragma once + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +// #define MTX_LOG_ENABLED +#include + +// typedef unsigned long long TIMESTAMP; +using blade_sample_type = std::complex; +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; + +template 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 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(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 00000000..62ee8488 --- /dev/null +++ b/Transceiver52M/ms/itrq.h @@ -0,0 +1,213 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +/* +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 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 lk(lr); + while (r_flag == 0) + cond_r.wait(lk); + r_flag--; + return 1; + } + ssize_t spsc_check_w() + { + std::unique_lock lk(lw); + while (w_flag == 0) + cond_w.wait(lk); + w_flag--; + return 1; + } + void spsc_notify_r() + { + std::unique_lock lk(lr); + r_flag++; + cond_r.notify_one(); + } + void spsc_notify_w() + { + std::unique_lock lk(lw); + w_flag++; + cond_w.notify_one(); + } +}; + +// originally designed for select loop integration +template 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 class T> +class spsc : public T { + static_assert(SZ > 0, "queues need a size..."); + std::atomic readptr; + std::atomic writeptr; + + ELEM buf[SZ]; + + public: + using base_t = T; + 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 +class spsc_evfd : public spsc_detail::spsc {}; +template +class spsc_cond : public spsc_detail::spsc {}; \ 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 00000000..9e9083f7 --- /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 + * (C) 2016-2017 by Vadim Yanitskiy + * (C) 2022 by by sysmocom - s.f.m.c. GmbH + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#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 00000000..50138719 --- /dev/null +++ b/Transceiver52M/ms/l1ctl_server_cb.cpp @@ -0,0 +1,77 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +extern "C" { +#include +#include +#include +} + +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 00000000..0857c656 --- /dev/null +++ b/Transceiver52M/ms/logging.cpp @@ -0,0 +1,103 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +extern "C" { +#include +#include +#include +#include + +} +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 00000000..3a62ed05 --- /dev/null +++ b/Transceiver52M/ms/ms.cpp @@ -0,0 +1,339 @@ + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ +#include "GSMCommon.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(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 auto parsec(std::vector &v, A &itr, std::string arg, bool *rv) +{ + if (*itr == arg) { + *rv = true; + return true; + } + return false; +} + +template +bool parsec(std::vector &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 bool parsec(std::vector &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 bool parsec(std::vector &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 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, >x); + parsec(args, i, "-tx", &tx_flag); + } + + std::cerr << "usage: " << argv[0] << " [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 00000000..8b781a3f --- /dev/null +++ b/Transceiver52M/ms/ms.h @@ -0,0 +1,264 @@ +#pragma once + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#if defined(BUILDBLADE) +#include "bladerf_specific.h" +#define BASET blade_hw +#elif defined(BUILDUHD) +#include "uhd_specific.h" +#define BASET uhd_hw +#elif defined(BUILDIPC) +#include "ipc_specific.h" +#define BASET ipc_hw +#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 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 +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)[i] = static_cast((reinterpret_cast(src)[i])) * scale; +} +template void convert_and_scale_default(void *dst, void *src, unsigned int src_len) +{ + return convert_and_scale(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 g(m); + global_time_keeper = t; + global_ts_keeper = ts; + } + void inc_both() + { + std::lock_guard 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 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 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 g(m); + global_time_keeper.decTN(1); + global_ts_keeper -= ONE_TS_BURST_LEN; + } + auto get_ts() + { + std::lock_guard g(m); + return global_ts_keeper; + } + auto gsmtime() + { + std::lock_guard g(m); + return global_time_keeper; + } + void get_both(GSM::Time *t, int64_t *ts) + { + std::lock_guard 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 rcv_done; + std::atomic sch_thread_done; + + int64_t temp_ts_corr_offset = 0; + int64_t first_sch_ts_start = -1; + + time_keeper timekeeper; + + void start(); + std::atomic 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 00000000..764a69e8 --- /dev/null +++ b/Transceiver52M/ms/ms_rx_burst.h @@ -0,0 +1,25 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#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 00000000..69fca5eb --- /dev/null +++ b/Transceiver52M/ms/ms_rx_burst_test.cpp @@ -0,0 +1,207 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ +#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)); + 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(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(burst.begin(), burst.begin(), ONE_TS_BURST_LEN * 2, + 1.f / float(scale)); + + std::complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + auto ss = reinterpret_cast *>(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(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 *>(burst.begin()); + float ncmax, dcmax; + std::complex 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, ¶m); + 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 00000000..780ab423 --- /dev/null +++ b/Transceiver52M/ms/ms_rx_lower.cpp @@ -0,0 +1,343 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include "sigProcLib.h" +#include "signalVector.h" +#include +#include +#include +#include +#include + +#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 *>(which_out_buffer); + std::complex 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(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(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(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 00000000..dd25fd28 --- /dev/null +++ b/Transceiver52M/ms/ms_upper.cpp @@ -0,0 +1,451 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include "sigProcLib.h" +#include "ms.h" +#include +#include +#include +#include "grgsm_vitac/grgsm_vitac.h" + +extern "C" { +#include +#include "sch.h" +#include "convolve.h" +#include "convert.h" +#include "proto_trxd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LSANDEBUG +void __lsan_do_recoverable_leak_check(); +#endif +} + +#include "ms_upper.h" + +namespace trxcon +{ +extern "C" { +#include +#include +#include +#include +#include +#include +#include + +// #include +#include +#include + +#include +#include +#include +#include +#include + +#include +// #include +} +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 *>(&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(wTime.FN()), static_cast(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(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 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 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(burstTime.FN()), static_cast(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(burst_buf, txburst->begin(), txburst->size() * 2, 1); +#ifdef TXDEBUG + auto check = signalVector(txburst->size(), 40); + convert_and_scale(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 00000000..a8a86a49 --- /dev/null +++ b/Transceiver52M/ms/ms_upper.h @@ -0,0 +1,57 @@ + +#pragma once + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ +#include +#include +#include + +#include "GSMCommon.h" +#include "radioClock.h" +#include "ms.h" + +namespace trxcon +{ +extern "C" { +#include +#include +} +} // 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 00000000..d05c4e26 --- /dev/null +++ b/Transceiver52M/ms/sch.c @@ -0,0 +1,324 @@ +/* + * (C) 2013 by Andreas Eversberg + * (C) 2015 by Alexander Chemeris + * (C) 2016 by Tom Tsou + * (C) 2017 by Harald Welte + * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH / Eric Wild + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 00000000..1af2114b --- /dev/null +++ b/Transceiver52M/ms/sch.h @@ -0,0 +1,48 @@ +#pragma once +/* + * (C) 2013 by Andreas Eversberg + * (C) 2015 by Alexander Chemeris + * (C) 2016 by Tom Tsou + * (C) 2017 by Harald Welte + * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH / Eric Wild + * + * 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 + +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 00000000..ed1a72a5 --- /dev/null +++ b/Transceiver52M/ms/uhd_specific.h @@ -0,0 +1,251 @@ +#pragma once + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Eric Wild + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using blade_sample_type = std::complex; +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; + +template 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 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 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 020f7e30..6ce9ad48 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