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. 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* see https://osmocom.org/projects/baseband/wiki/MS-side_GPRS for the project description and details Change-Id: I36c65a8c725c4da76dc70006cd96b0a2b6878e84changes/16/30416/33
parent
5a23a24bb1
commit
b7253c6fdc
|
@ -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
|
||||
|
|
|
@ -26,8 +26,14 @@ 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 += \
|
||||
CommonLibs \
|
||||
GSM \
|
||||
Transceiver52M \
|
||||
|
|
|
@ -73,6 +73,36 @@ COMMON_LDADD = \
|
|||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMOVTY_LIBS)
|
||||
|
||||
if ENABLE_MS_TRX
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/
|
||||
AM_CPPFLAGS += -I${srcdir}
|
||||
|
||||
TRXCON_LDADD = \
|
||||
$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a \
|
||||
$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \
|
||||
$(LIBOSMOCODING_LIBS)
|
||||
|
||||
MS_SOURCES = \
|
||||
ms/sch.c \
|
||||
ms/ms.cpp \
|
||||
ms/ms_rx_lower.cpp \
|
||||
grgsm_vitac/grgsm_vitac.cpp \
|
||||
grgsm_vitac/viterbi_detector.cc
|
||||
|
||||
noinst_HEADERS += \
|
||||
ms/ms.h \
|
||||
ms/bladerf_specific.h \
|
||||
ms/uhd_specific.h \
|
||||
ms/ms_rx_burst.h \
|
||||
ms/ms_upper.h \
|
||||
ms/itrq.h \
|
||||
ms/sch.h \
|
||||
grgsm_vitac/viterbi_detector.h \
|
||||
grgsm_vitac/constants.h \
|
||||
grgsm_vitac/grgsm_vitac.h
|
||||
|
||||
endif
|
||||
|
||||
bin_PROGRAMS =
|
||||
|
||||
if DEVICE_UHD
|
||||
|
@ -83,6 +113,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.c 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 +164,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.c 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
|
||||
|
@ -124,4 +193,3 @@ osmo_trx_ipc_LDADD = \
|
|||
$(COMMON_LDADD)
|
||||
osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
endif
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ void initvita() {
|
|||
|
||||
}
|
||||
|
||||
MULTI_VER_TARGET_ATTR
|
||||
MULTI_VER_TARGET_ATTR NO_UBSAN
|
||||
void detect_burst(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary)
|
||||
{
|
||||
std::vector<gr_complex> rhh_temp(CHAN_IMP_RESP_LENGTH * d_OSR);
|
||||
|
|
|
@ -35,13 +35,21 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
/* ... but apaprently clang disagrees... */
|
||||
/* ... but apparently clang disagrees... */
|
||||
#if defined(__clang__)
|
||||
#define MULTI_VER_TARGET_ATTR_CLANGONLY MULTI_VER_TARGET_ATTR
|
||||
#else
|
||||
#define MULTI_VER_TARGET_ATTR_CLANGONLY
|
||||
#endif
|
||||
|
||||
#if defined(__has_attribute)
|
||||
#if __has_attribute(no_sanitize)
|
||||
#define NO_UBSAN __attribute__((no_sanitize("undefined")))
|
||||
#endif
|
||||
#else
|
||||
#define NO_UBSAN
|
||||
#endif
|
||||
|
||||
#define SYNC_SEARCH_RANGE 30
|
||||
const int d_OSR(4);
|
||||
|
||||
|
|
|
@ -0,0 +1,451 @@
|
|||
#pragma once
|
||||
/*
|
||||
* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Eric Wild <ewild@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "itrq.h"
|
||||
#include <atomic>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include <libbladeRF.h>
|
||||
#include <Timeval.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const size_t BLADE_BUFFER_SIZE = 1024 * 1;
|
||||
const size_t BLADE_NUM_BUFFERS = 32 * 1;
|
||||
const size_t NUM_TRANSFERS = 16 * 2;
|
||||
const int SAMPLE_SCALE_FACTOR = 15; // actually 16 but sigproc complains about clipping..
|
||||
|
||||
// see https://en.cppreference.com/w/cpp/language/parameter_pack "Brace-enclosed initializers" example
|
||||
template <typename Arg, typename... Args> void expand_args(std::ostream &out, Arg &&arg, Args &&...args)
|
||||
{
|
||||
out << '(' << std::forward<Arg>(arg);
|
||||
(void)(int[]){ 0, (void((out << "," << std::forward<Args>(args))), 0)... };
|
||||
out << ')' << std::endl;
|
||||
}
|
||||
|
||||
template <class R, class... Args> using RvalFunc = R (*)(Args...);
|
||||
|
||||
template <class R, class... Args>
|
||||
R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finame, const char *funcname, int line,
|
||||
Args... args)
|
||||
{
|
||||
R rval = func(std::forward<Args>(args)...);
|
||||
if (rval != 0) {
|
||||
std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':'
|
||||
<< funcname << ':' << fname;
|
||||
expand_args(std::cerr, args...);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
// only macros can pass a func name string
|
||||
#define blade_check(func, ...) exec_and_check(func, #func, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
using blade_sample_type = std::complex<int16_t>;
|
||||
enum class blade_speed_buffer_type { HS, SS };
|
||||
template <blade_speed_buffer_type T> struct blade_usb_message {
|
||||
uint32_t reserved;
|
||||
uint64_t ts;
|
||||
uint32_t meta_flags;
|
||||
blade_sample_type d[(T == blade_speed_buffer_type::SS ? 512 : 256) - 4];
|
||||
};
|
||||
|
||||
static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::SS>) == 2048, "blade buffer mismatch!");
|
||||
static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::HS>) == 1024, "blade buffer mismatch!");
|
||||
template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer {
|
||||
static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!");
|
||||
blade_usb_message<T> m[SZ];
|
||||
int actual_samples_per_msg()
|
||||
{
|
||||
return sizeof(blade_usb_message<T>::d) / sizeof(typeof(blade_usb_message<T>::d[0]));
|
||||
}
|
||||
int actual_samples_per_buffer()
|
||||
{
|
||||
return SZ * actual_samples_per_msg();
|
||||
}
|
||||
int samples_per_buffer()
|
||||
{
|
||||
return SZ * sizeof(blade_usb_message<T>) / sizeof(typeof(blade_usb_message<T>::d[0]));
|
||||
}
|
||||
int num_msgs_per_buffer()
|
||||
{
|
||||
return SZ;
|
||||
}
|
||||
auto get_first_ts()
|
||||
{
|
||||
return m[0].ts;
|
||||
}
|
||||
constexpr auto *getsampleoffset(int ofs)
|
||||
{
|
||||
auto full = ofs / actual_samples_per_msg();
|
||||
auto rem = ofs % actual_samples_per_msg();
|
||||
return &m[full].d[rem];
|
||||
}
|
||||
int readall(blade_sample_type *outaddr)
|
||||
{
|
||||
blade_sample_type *addr = outaddr;
|
||||
for (unsigned 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 (unsigned int i = 0; i < SZ; i++) {
|
||||
m[i] = {};
|
||||
m[i].ts = first_ts + i * actual_samples_per_msg();
|
||||
if (len_rem) {
|
||||
int max_to_copy =
|
||||
len_rem > actual_samples_per_msg() ? actual_samples_per_msg() : len_rem;
|
||||
memcpy(&m[i].d[0], in, max_to_copy * sizeof(blade_sample_type));
|
||||
len_rem -= max_to_copy;
|
||||
in += actual_samples_per_msg();
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer_helper {
|
||||
static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!");
|
||||
static blade_otw_buffer<SZ / 512, T> x;
|
||||
};
|
||||
|
||||
using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_buffer_type::SS>::x);
|
||||
// using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>;
|
||||
using bh_fn_t = std::function<int(dev_buf_t *)>;
|
||||
|
||||
template <typename T> struct blade_hw {
|
||||
struct bladerf *dev;
|
||||
struct bladerf_stream *rx_stream;
|
||||
struct bladerf_stream *tx_stream;
|
||||
// using pkt2buf = blade_otw_buffer<2, blade_speed_buffer_type::SS>;
|
||||
using tx_buf_q_type = spsc_cond<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>;
|
||||
const unsigned int rxFullScale, txFullScale;
|
||||
const int rxtxdelay;
|
||||
|
||||
float rxgain, txgain;
|
||||
static std::atomic<bool> stop_me_flag;
|
||||
|
||||
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 (tx_stream) {
|
||||
bladerf_deinit_stream(tx_stream);
|
||||
}
|
||||
|
||||
if (rx_stream) {
|
||||
bladerf_deinit_stream(rx_stream);
|
||||
}
|
||||
|
||||
bladerf_enable_module(dev, BLADERF_MODULE_RX, false);
|
||||
bladerf_enable_module(dev, BLADERF_MODULE_TX, false);
|
||||
|
||||
bladerf_close(dev);
|
||||
dev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int init_device(bh_fn_t rxh, bh_fn_t txh)
|
||||
{
|
||||
struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)) * 64, 6 * 64 }, actual;
|
||||
|
||||
bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_DEBUG);
|
||||
bladerf_set_usb_reset_on_open(true);
|
||||
blade_check(bladerf_open, &dev, "");
|
||||
if (!dev) {
|
||||
std::cerr << "open failed, device missing?" << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
if (bladerf_device_speed(dev) != bladerf_dev_speed::BLADERF_DEVICE_SPEED_SUPER) {
|
||||
std::cerr << "open failed, only superspeed (usb3) supported!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
blade_check(bladerf_set_tuning_mode, dev, bladerf_tuning_mode::BLADERF_TUNING_MODE_FPGA);
|
||||
|
||||
bool is_locked;
|
||||
blade_check(bladerf_set_pll_enable, dev, true);
|
||||
blade_check(bladerf_set_pll_refclk, dev, 10000000UL);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
usleep(50 * 1000);
|
||||
bladerf_get_pll_lock_state(dev, &is_locked);
|
||||
|
||||
if (is_locked)
|
||||
break;
|
||||
}
|
||||
if (!is_locked) {
|
||||
std::cerr << "unable to lock refclk!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
|
||||
blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
|
||||
|
||||
blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)cfg.rx_freq);
|
||||
blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)cfg.tx_freq);
|
||||
|
||||
blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)cfg.bandwidth,
|
||||
(bladerf_bandwidth *)NULL);
|
||||
blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)cfg.bandwidth,
|
||||
(bladerf_bandwidth *)NULL);
|
||||
|
||||
blade_check(bladerf_set_gain_mode, dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC);
|
||||
blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)30);
|
||||
blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)30);
|
||||
usleep(1000);
|
||||
blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true);
|
||||
usleep(1000);
|
||||
blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true);
|
||||
usleep(1000);
|
||||
blade_check(bladerf_init_stream, &rx_stream, dev, getrxcb(rxh), &buf_mgmt.rx_samples, BLADE_NUM_BUFFERS,
|
||||
BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
|
||||
|
||||
blade_check(bladerf_init_stream, &tx_stream, dev, gettxcb(txh), &buf_mgmt.tx_samples, BLADE_NUM_BUFFERS,
|
||||
BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
|
||||
|
||||
for (unsigned int i = 0; i < BLADE_NUM_BUFFERS; i++) {
|
||||
auto cur_buffer = reinterpret_cast<tx_buf_q_type::elem_t *>(buf_mgmt.tx_samples);
|
||||
buf_mgmt.bufptrqueue.spsc_push(&cur_buffer[i]);
|
||||
}
|
||||
|
||||
setRxGain(20);
|
||||
setTxGain(30);
|
||||
|
||||
usleep(1000);
|
||||
|
||||
// bladerf_set_stream_timeout(dev, BLADERF_TX, 4);
|
||||
// bladerf_set_stream_timeout(dev, BLADERF_RX, 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool tuneTx(double freq, size_t chan = 0)
|
||||
{
|
||||
msleep(15);
|
||||
blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq);
|
||||
msleep(15);
|
||||
return true;
|
||||
};
|
||||
bool tuneRx(double freq, size_t chan = 0)
|
||||
{
|
||||
msleep(15);
|
||||
blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq);
|
||||
msleep(15);
|
||||
return true;
|
||||
};
|
||||
bool tuneRxOffset(double offset, size_t chan = 0)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
double setRxGain(double dB, size_t chan = 0)
|
||||
{
|
||||
rxgain = dB;
|
||||
msleep(15);
|
||||
blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)dB);
|
||||
msleep(15);
|
||||
return dB;
|
||||
};
|
||||
double setTxGain(double dB, size_t chan = 0)
|
||||
{
|
||||
txgain = dB;
|
||||
msleep(15);
|
||||
blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)dB);
|
||||
msleep(15);
|
||||
return dB;
|
||||
};
|
||||
int setPowerAttenuation(int atten, size_t chan = 0)
|
||||
{
|
||||
return atten;
|
||||
};
|
||||
|
||||
static void check_timestamp(dev_buf_t *rcd)
|
||||
{
|
||||
static bool first = true;
|
||||
static uint64_t last_ts;
|
||||
if (first) {
|
||||
first = false;
|
||||
last_ts = rcd->m[0].ts;
|
||||
} else if (last_ts + rcd->actual_samples_per_buffer() != rcd->m[0].ts) {
|
||||
std::cerr << "RX Overrun!" << last_ts << " " << rcd->actual_samples_per_buffer() << " "
|
||||
<< last_ts + rcd->actual_samples_per_buffer() << " " << rcd->m[0].ts << std::endl;
|
||||
last_ts = rcd->m[0].ts;
|
||||
} else {
|
||||
last_ts = rcd->m[0].ts;
|
||||
}
|
||||
}
|
||||
|
||||
bladerf_stream_cb getrxcb(bh_fn_t rxbh)
|
||||
{
|
||||
// C cb -> no capture!
|
||||
static auto rxbhfn = rxbh;
|
||||
return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta,
|
||||
void *samples, size_t num_samples, void *user_data) -> void * {
|
||||
// struct blade_hw *trx = (struct blade_hw *)user_data;
|
||||
static int to_skip = 0;
|
||||
dev_buf_t *rcd = (dev_buf_t *)samples;
|
||||
|
||||
if (stop_me_flag)
|
||||
return BLADERF_STREAM_SHUTDOWN;
|
||||
|
||||
if (to_skip < 120) // prevents weird overflows on startup
|
||||
to_skip++;
|
||||
else {
|
||||
check_timestamp(rcd);
|
||||
rxbhfn(rcd);
|
||||
}
|
||||
|
||||
return samples;
|
||||
};
|
||||
}
|
||||
bladerf_stream_cb gettxcb(bh_fn_t txbh)
|
||||
{
|
||||
// C cb -> no capture!
|
||||
static auto txbhfn = txbh;
|
||||
return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta,
|
||||
void *samples, size_t num_samples, void *user_data) -> void * {
|
||||
struct blade_hw *trx = (struct blade_hw *)user_data;
|
||||
auto ptr = reinterpret_cast<tx_buf_q_type::elem_t>(samples);
|
||||
|
||||
if (samples) // put buffer address back into queue, ready to be reused
|
||||
trx->buf_mgmt.bufptrqueue.spsc_push(&ptr);
|
||||
|
||||
if (stop_me_flag)
|
||||
return BLADERF_STREAM_SHUTDOWN;
|
||||
|
||||
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 0;
|
||||
};
|
||||
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 0;
|
||||
};
|
||||
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);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,194 @@
|
|||
#pragma once
|
||||
/*
|
||||
* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Eric Wild <ewild@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace spsc_detail
|
||||
{
|
||||
template <bool block_read, bool block_write> class spsc_cond_detail {
|
||||
std::condition_variable cond_r, cond_w;
|
||||
std::mutex lr, lw;
|
||||
std::atomic_int r_flag, w_flag;
|
||||
|
||||
public:
|
||||
explicit spsc_cond_detail() : r_flag(0), w_flag(0)
|
||||
{
|
||||
}
|
||||
|
||||
~spsc_cond_detail()
|
||||
{
|
||||
}
|
||||
|
||||
ssize_t spsc_check_r()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(lr);
|
||||
while (r_flag == 0)
|
||||
cond_r.wait(lk);
|
||||
r_flag--;
|
||||
return 1;
|
||||
}
|
||||
ssize_t spsc_check_w()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(lw);
|
||||
while (w_flag == 0)
|
||||
cond_w.wait(lk);
|
||||
w_flag--;
|
||||
return 1;
|
||||
}
|
||||
void spsc_notify_r()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(lr);
|
||||
r_flag++;
|
||||
cond_r.notify_one();
|
||||
}
|
||||
void spsc_notify_w()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(lw);
|
||||
w_flag++;
|
||||
cond_w.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
// originally designed for select loop integration
|
||||
template <bool block_read, bool block_write> class spsc_efd_detail {
|
||||
int efd_r, efd_w; /* eventfds used to block/notify readers/writers */
|
||||
|
||||
public:
|
||||
explicit spsc_efd_detail()
|
||||
: efd_r(eventfd(0, block_read ? 0 : EFD_NONBLOCK)), efd_w(eventfd(1, block_write ? 0 : EFD_NONBLOCK))
|
||||
{
|
||||
}
|
||||
|
||||
~spsc_efd_detail()
|
||||
{
|
||||
close(efd_r);
|
||||
close(efd_w);
|
||||
}
|
||||
|
||||
ssize_t spsc_check_r()
|
||||
{
|
||||
uint64_t efdr;
|
||||
return read(efd_r, &efdr, sizeof(uint64_t));
|
||||
}
|
||||
ssize_t spsc_check_w()
|
||||
{
|
||||
uint64_t efdr;
|
||||
return read(efd_w, &efdr, sizeof(uint64_t));
|
||||
}
|
||||
void spsc_notify_r()
|
||||
{
|
||||
uint64_t efdu = 1;
|
||||
write(efd_r, &efdu, sizeof(uint64_t));
|
||||
}
|
||||
void spsc_notify_w()
|
||||
{
|
||||
uint64_t efdu = 1;
|
||||
write(efd_w, &efdu, sizeof(uint64_t));
|
||||
}
|
||||
int get_r_efd()
|
||||
{
|
||||
return efd_r;
|
||||
}
|
||||
int get_w_efd()
|
||||
{
|
||||
return efd_w;
|
||||
}
|
||||
};
|
||||
|
||||
template <unsigned int SZ, typename ELEM, bool block_read, bool block_write, template <bool, bool> class T>
|
||||
class spsc : public T<block_read, block_write> {
|
||||
static_assert(SZ > 0, "queues need a size...");
|
||||
std::atomic<unsigned int> readptr;
|
||||
std::atomic<unsigned int> writeptr;
|
||||
|
||||
ELEM buf[SZ];
|
||||
|
||||
public:
|
||||
using base_t = T<block_read, block_write>;
|
||||
using elem_t = ELEM;
|
||||
explicit spsc() : readptr(0), writeptr(0)
|
||||
{
|
||||
}
|
||||
|
||||
~spsc()
|
||||
{
|
||||
}
|
||||
|
||||
/*! Adds element to the queue by copying the data.
|
||||
* \param[in] elem input buffer, must match the originally configured queue buffer size!.
|
||||
* \returns true if queue was not full and element was successfully pushed */
|
||||
bool spsc_push(const ELEM *elem)
|
||||
{
|
||||
size_t cur_wp, cur_rp;
|
||||
cur_wp = writeptr.load(std::memory_order_relaxed);
|
||||
cur_rp = readptr.load(std::memory_order_acquire);
|
||||
if ((cur_wp + 1) % SZ == cur_rp) {
|
||||
if (block_write)
|
||||
base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */
|
||||
return false;
|
||||
}
|
||||
buf[cur_wp] = *elem;
|
||||
writeptr.store((cur_wp + 1) % SZ, std::memory_order_release);
|
||||
if (block_read)
|
||||
base_t::spsc_notify_r(); /* fine after release */
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Removes element from the queue by copying the data.
|
||||
* \param[in] elem output buffer, must match the originally configured queue buffer size!.
|
||||
* \returns true if queue was not empty and element was successfully removed */
|
||||
bool spsc_pop(ELEM *elem)
|
||||
{
|
||||
size_t cur_wp, cur_rp;
|
||||
cur_wp = writeptr.load(std::memory_order_acquire);
|
||||
cur_rp = readptr.load(std::memory_order_relaxed);
|
||||
|
||||
if (cur_wp == cur_rp) /* blocks via prep_pop */
|
||||
return false;
|
||||
|
||||
*elem = buf[cur_rp];
|
||||
readptr.store((cur_rp + 1) % SZ, std::memory_order_release);
|
||||
if (block_write)
|
||||
base_t::spsc_notify_w();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks.
|
||||
* This function can be used to deliberately wait for a non-empty queue on the read side.
|
||||
* \returns result of reading the fd. */
|
||||
ssize_t spsc_prep_pop()
|
||||
{
|
||||
return base_t::spsc_check_r();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace spsc_detail
|
||||
|
||||
template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
|
||||
class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {};
|
||||
template <unsigned int SZ, typename ELEM, bool block_read, bool block_write>
|
||||
class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {};
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* OsmocomBB <-> SDR connection bridge
|
||||
* UNIX socket server for L1CTL
|
||||
*
|
||||
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
|
||||
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
* (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
|
||||
#include <osmocom/bb/trxcon/logging.h>
|
||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
|
||||
|
||||
#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args)
|
||||
|
||||
static int l1ctl_client_read_cb(struct osmo_fd *ofd)
|
||||
{
|
||||
struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
|
||||
struct msgb *msg;
|
||||
uint16_t len;
|
||||
int rc;
|
||||
|
||||
/* Attempt to read from socket */
|
||||
rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
|
||||
if (rc != L1CTL_MSG_LEN_FIELD) {
|
||||
if (rc <= 0) {
|
||||
LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc,
|
||||
strerror(errno));
|
||||
} else {
|
||||
LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n");
|
||||
rc = -EIO;
|
||||
}
|
||||
l1ctl_client_conn_close(client);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check message length */
|
||||
len = ntohs(len);
|
||||
if (len > L1CTL_LENGTH) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allocate a new msg */
|
||||
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg");
|
||||
if (!msg) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
msg->l1h = msgb_put(msg, len);
|
||||
rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
|
||||
if (rc != len) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno));
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Debug print */
|
||||
LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
|
||||
|
||||
/* Call L1CTL handler */
|
||||
client->server->cfg->conn_read_cb(client, msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
|
||||
{
|
||||
struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
|
||||
int len;
|
||||
|
||||
if (ofd->fd <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
len = write(ofd->fd, msg->data, msg->len);
|
||||
if (len != msg->len) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len,
|
||||
msg->len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Connection handler */
|
||||
static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
|
||||
{
|
||||
struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
|
||||
struct l1ctl_client *client;
|
||||
int rc, client_fd;
|
||||
|
||||
client_fd = accept(sfd->fd, NULL, NULL);
|
||||
if (client_fd < 0) {
|
||||
LOGP(DL1C, LOGL_ERROR,
|
||||
"Failed to accept() a new connection: "
|
||||
"%s\n",
|
||||
strerror(errno));
|
||||
return client_fd;
|
||||
}
|
||||
|
||||
if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
|
||||
server->num_clients >= server->cfg->num_clients_max) {
|
||||
LOGP(DL1C, LOGL_NOTICE,
|
||||
"L1CTL server cannot accept more "
|
||||
"than %u connection(s)\n",
|
||||
server->cfg->num_clients_max);
|
||||
close(client_fd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
client = talloc_zero(server, struct l1ctl_client);
|
||||
if (client == NULL) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
|
||||
close(client_fd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Init the client's write queue */
|
||||
osmo_wqueue_init(&client->wq, 100);
|
||||
INIT_LLIST_HEAD(&client->wq.bfd.list);
|
||||
|
||||
client->wq.write_cb = &l1ctl_client_write_cb;
|
||||
client->wq.read_cb = &l1ctl_client_read_cb;
|
||||
osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
|
||||
|
||||
/* Register the client's write queue */
|
||||
rc = osmo_fd_register(&client->wq.bfd);
|
||||
if (rc != 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
|
||||
close(client->wq.bfd.fd);
|
||||
talloc_free(client);
|
||||
return rc;
|
||||
}
|
||||
|
||||
llist_add_tail(&client->list, &server->clients);
|
||||
client->id = server->next_client_id++;
|
||||
client->server = server;
|
||||
server->num_clients++;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
|
||||
|
||||
if (client->server->cfg->conn_accept_cb != NULL)
|
||||
client->server->cfg->conn_accept_cb(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
|
||||
{
|
||||
uint8_t *len;
|
||||
|
||||
/* Debug print */
|
||||
LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
|
||||
|
||||
if (msg->l1h != msg->data)
|
||||
LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
|
||||
|
||||
/* Prepend 16-bit length before sending */
|
||||
len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
|
||||
osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
|
||||
|
||||
if (osmo_wqueue_enqueue(&client->wq, msg) != 0) {
|
||||
LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
|
||||
msgb_free(msg);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void l1ctl_client_conn_close(struct l1ctl_client *client)
|
||||
{
|
||||
struct l1ctl_server *server = client->server;
|
||||
|
||||
LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
|
||||
|
||||
if (server->cfg->conn_close_cb != NULL)
|
||||
server->cfg->conn_close_cb(client);
|
||||
|
||||
/* Close connection socket */
|
||||
osmo_fd_unregister(&client->wq.bfd);
|
||||
close(client->wq.bfd.fd);
|
||||
client->wq.bfd.fd = -1;
|
||||
|
||||
/* Clear pending messages */
|
||||
osmo_wqueue_clear(&client->wq);
|
||||
|
||||
client->server->num_clients--;
|
||||
llist_del(&client->list);
|
||||
talloc_free(client);
|
||||
|
||||
/* If this was the last client, reset the client IDs generator to 0.
|
||||
* This way avoid assigning huge unreadable client IDs like 26545. */
|
||||
if (llist_empty(&server->clients))
|
||||
server->next_client_id = 0;
|
||||
}
|
||||
|
||||
struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
|
||||
{
|
||||
struct l1ctl_server *server;
|
||||
int rc;
|
||||
|
||||
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
|
||||
|
||||
server = talloc(ctx, struct l1ctl_server);
|
||||
OSMO_ASSERT(server != NULL);
|
||||
|
||||
*server = (struct l1ctl_server){
|
||||
.clients = LLIST_HEAD_INIT(server->clients),
|
||||
.cfg = cfg,
|
||||
};
|
||||
|
||||
/* conn_read_cb shall not be NULL */
|
||||
OSMO_ASSERT(cfg->conn_read_cb != NULL);
|
||||
|
||||
/* Bind connection handler */
|
||||
osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
|
||||
|
||||
rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno));
|
||||
talloc_free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void l1ctl_server_free(struct l1ctl_server *server)
|
||||
{
|
||||
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
|
||||
|
||||
/* Close all client connections */
|
||||
while (!llist_empty(&server->clients)) {
|
||||
struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list);
|
||||
l1ctl_client_conn_close(client);
|
||||
}
|
||||
|
||||
/* Unbind listening socket */
|
||||
if (server->ofd.fd != -1) {
|
||||
osmo_fd_unregister(&server->ofd);
|
||||
close(server->ofd.fd);
|
||||
server->ofd.fd = -1;
|
||||
}
|
||||
|
||||
talloc_free(server);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Eric Wild <ewild@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
extern "C" {
|
||||
#include <osmocom/bb/trxcon/trxcon.h>
|
||||
#include <osmocom/bb/trxcon/trxcon_fsm.h>
|
||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
|
||||
}
|
||||
|
||||
static struct l1ctl_server_cfg server_cfg;
|
||||
static struct l1ctl_server *server = NULL;
|
||||
namespace trxcon
|
||||
{
|
||||
extern struct trxcon_inst *g_trxcon;
|
||||
}
|
||||
|
||||
static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg)
|
||||
{
|
||||
struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
|
||||
|
||||
return trxcon_l1ctl_receive(trxcon, msg);
|
||||
}
|
||||
|
||||
static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c)
|
||||
{
|
||||
l1c->log_prefix = talloc_strdup(l1c, trxcon::g_trxcon->log_prefix);
|
||||
l1c->priv = trxcon::g_trxcon;
|
||||
trxcon::g_trxcon->l2if = l1c;
|
||||
}
|
||||
|
||||
static void l1ctl_conn_close_cb(struct l1ctl_client *l1c)
|
||||
{
|
||||
struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
|
||||
|
||||
if (trxcon == NULL || trxcon->fi == NULL)
|
||||
return;
|
||||
|
||||
osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL);
|
||||
}
|
||||
|
||||
namespace trxcon
|
||||
{
|
||||
bool trxc_l1ctl_init(void *tallctx)
|
||||
{
|
||||
/* Start the L1CTL server */
|
||||
server_cfg = (struct l1ctl_server_cfg){
|
||||
/* TODO: make path configurable */
|
||||
.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 false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace trxcon
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* 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 <osmocom/core/application.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/bb/trxcon/logging.h>
|
||||
#include <osmocom/bb/trxcon/trxcon.h>
|
||||
|
||||
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,
|
||||
},
|
||||
[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),
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
void trxc_log_init(void *tallctx)
|
||||
{
|
||||
osmo_init_logging2(tallctx, &trxcon_log_info);
|
||||
|
||||
trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg));
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
|
||||
/*
|
||||
* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Eric Wild <ewild@sysmocom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "GSMCommon.h"
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <complex>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
|
||||
#include "sigProcLib.h"
|
||||
|
||||
#include "ms.h"
|
||||
#include "ms_rx_burst.h"
|
||||
#include "grgsm_vitac/grgsm_vitac.h"
|
||||
|
||||
extern "C" {
|
||||
#include "sch.h"
|
||||
#include "convolve.h"
|
||||
#include "convert.h"
|
||||
}
|
||||
|
||||
dummylog ms_trx::dummy_log;
|
||||
|
||||
#ifdef DBGXX
|
||||
const int offsetrange = 200;
|
||||
const int offset_start = -15;
|
||||
static int offset_ctr = 0;
|
||||
#endif
|
||||
|
||||
template <> std::atomic<bool> ms_trx::base::stop_me_flag(false);
|
||||
|
||||
void tx_test(ms_trx *t, ts_hitter_q_t *q, unsigned int *tsc)
|
||||
{
|
||||
sched_param sch_params;
|
||||
sch_params.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
||||
pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch_params);
|
||||
|
||||
auto burst = genRandAccessBurst(0, 4, 0);
|
||||
scaleVector(*burst, t->txFullScale * 0.7);
|
||||
|
||||
// float -> int16
|
||||
blade_sample_type burst_buf[burst->size()];
|
||||
convert_and_scale<int16_t, float>(burst_buf, burst->begin(), burst->size() * 2, 1);
|
||||
|
||||
while (1) {
|
||||
GSM::Time target;
|
||||
while (!q->spsc_pop(&target)) {
|
||||
q->spsc_prep_pop();
|
||||
}
|
||||
|
||||
std::cerr << std::endl << "\x1B[32m hitting " << target.FN() << "\033[0m" << std::endl;
|
||||
|
||||
int timing_advance = 0;
|
||||
int64_t now_ts;
|
||||
GSM::Time now_time;
|
||||
target.incTN(3); // ul dl offset
|
||||
int target_fn = target.FN();
|
||||
int target_tn = target.TN();
|
||||
t->timekeeper.get_both(&now_time, &now_ts);
|
||||
|
||||
auto diff_fn = GSM::FNDelta(target_fn, now_time.FN());
|
||||
int diff_tn = (target_tn - (int)now_time.TN()) % 8;
|
||||
auto tosend = GSM::Time(diff_fn, 0);
|
||||
|
||||
if (diff_tn > 0)
|
||||
tosend.incTN(diff_tn);
|
||||
else if (diff_tn < 0)
|
||||
tosend.decTN(-diff_tn);
|
||||
|
||||
// in thory fn equal and tn+3 equal is also a problem...
|
||||
if (diff_fn < 0 || (diff_fn == 0 && (now_time.TN() - target_tn < 1))) {
|
||||
std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
|
||||
<< " tn OTHER: " << target_tn << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto check = now_time + tosend;
|
||||
int64_t send_ts =
|
||||
now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
|
||||
|
||||
#ifdef DBGXX
|
||||
std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn << " tn LOCAL: " << now_time.TN()
|
||||
<< " tn OTHER: " << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":"
|
||||
<< tosend.TN() << " calc: " << check.FN() << ":" << check.TN() << " target: " << target.FN()
|
||||
<< ":" << target.TN() << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
|
||||
#endif
|
||||
|
||||
unsigned int pad = 4 * 25;
|
||||
blade_sample_type buf2[burst->size() + pad];
|
||||
std::fill(buf2, buf2 + pad, 0);
|
||||
|
||||
memcpy(&buf2[pad], burst_buf, burst->size() * sizeof(blade_sample_type));
|
||||
|
||||
assert(target.FN() == check.FN());
|
||||
assert(target.TN() == check.TN());
|
||||
assert(target.FN() % 51 == 21);
|
||||
#ifdef DBGXX
|
||||
auto this_offset = offset_start + (offset_ctr++ % offsetrange);
|
||||
std::cerr << "-- O " << this_offset << std::endl;
|
||||
send_ts = now_ts - timing_advance +
|
||||
((target.FN() * 8 + (int)target.TN()) - (now_time.FN() * 8 + (int)now_time.TN())) *
|
||||
ONE_TS_BURST_LEN;
|
||||
#endif
|
||||
t->submit_burst_ts(buf2, burst->size() + pad, send_ts - pad);
|
||||
#ifdef DBGXX
|
||||
signalVector test(burst->size() + pad);
|
||||
convert_and_scale<float, int16_t>(test.begin(), buf2, burst->size() * 2 + pad, 1.f / float(scale));
|
||||
estim_burst_params ebp;
|
||||
auto det = detectAnyBurst(test, 0, 4, 4, CorrType::RACH, 40, &ebp);
|
||||
if (det > 0)
|
||||
std::cerr << "## Y " << ebp.toa << std::endl;
|
||||
else
|
||||
std::cerr << "## NOOOOOOOOO " << ebp.toa << std::endl;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifdef SYNCTHINGONLY
|
||||
template <typename A> auto parsec(std::vector<std::string> &v, A &itr, std::string arg, bool *rv)
|
||||
{
|
||||
if (*itr == arg) {
|
||||
*rv = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename A, typename B, typename C>
|
||||
bool parsec(std::vector<std::string> &v, A &itr, std::string arg, B f, C *rv)
|
||||
{
|
||||
if (*itr == arg) {
|
||||
itr++;
|
||||
if (itr != v.end()) {
|
||||
*rv = f(itr->c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, int *rv)
|
||||
{
|
||||
return parsec(
|
||||
v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv);
|
||||
}
|
||||
template <typename A> bool parsec(std::vector<std::string> &v, A &itr, std::string arg, int scale, unsigned int *rv)
|
||||
{
|
||||
return parsec(
|
||||
v, itr, arg, [scale](const char *v) -> auto{ return atoi(v) * scale; }, rv);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
cpu_set_t cpuset;
|
||||
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(2, &cpuset);
|
||||
|
||||
auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
|
||||
if (rv < 0) {
|
||||
std::cerr << "affinity: errreur! " << std::strerror(errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int default_tx_freq(881000 * 1000), default_rx_freq(926000 * 1000);
|
||||
unsigned int grx = 20, gtx = 20;
|
||||
bool tx_flag = false;
|
||||
pthread_setname_np(pthread_self(), "main");
|
||||
convolve_init();
|
||||
convert_init();
|
||||
sigProcLibSetup();
|
||||
initvita();
|
||||
|
||||
int status = 0;
|
||||
auto trx = new ms_trx();
|
||||
trx->do_auto_gain = true;
|
||||
|