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: I36c65a8c725c4da76dc70006cd96b0a2b6878e84
diff --git a/Transceiver52M/ms/bladerf_specific.h b/Transceiver52M/ms/bladerf_specific.h
new file mode 100644
index 0000000..d5088a7
--- /dev/null
+++ b/Transceiver52M/ms/bladerf_specific.h
@@ -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);
+ }
+};