Timur Davydov has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-bts/+/42703?usp=email )
Change subject: trx, bts: add optional WebSDR backend (callback-based transport) ......................................................................
trx, bts: add optional WebSDR backend (callback-based transport)
Introduce WebSDR backend enabled via ENABLE_WEBSDR, adding an alternative callback-based transport alongside existing socket-based TRX/OSMUX I/O
Implement: - TRX control via osmotrxlib_process_command() - TRXD data via apitrx_tx_call()/trx_data_read() - clock handling via apibts_clock_ind() and external timers - Osmux integration via ws_osmux_* helpers
Adapt scheduler to support API-based burst submission and external timer callbacks in WebSDR mode
Disable signal handling, stats and socket I/O when building with ENABLE_WEBSDR
Add WebSDR API entrypoint, Emscripten build target and example configuration
Enables running BTS/TRX stack in WebAssembly environments
Change-Id: Idebd588f33afd85021813ad5821391781db683a9 --- M .gitignore A doc/examples/trx/osmo-bts-trx-websdr.cfg M src/common/main.c M src/common/osmux.c M src/osmo-bts-trx/Makefile.am M src/osmo-bts-trx/main.c A src/osmo-bts-trx/osmo-bts-trx-websdr.c A src/osmo-bts-trx/osmo-bts-trx-websdr.h M src/osmo-bts-trx/scheduler_trx.c M src/osmo-bts-trx/trx_if.c M src/osmo-bts-trx/trx_vty.c 11 files changed, 709 insertions(+), 4 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-bts refs/changes/03/42703/1
diff --git a/.gitignore b/.gitignore index 34d55b8..af47de4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.o *.a +*.la +*.wasm +src/osmo-bts-trx/osmo-bts-trx-websdr.js Makefile.in Makefile .deps diff --git a/doc/examples/trx/osmo-bts-trx-websdr.cfg b/doc/examples/trx/osmo-bts-trx-websdr.cfg new file mode 100644 index 0000000..a71075e --- /dev/null +++ b/doc/examples/trx/osmo-bts-trx-websdr.cfg @@ -0,0 +1,71 @@ +! +! OsmoBTS () configuration for WebSDR +!! +! +no log stderr +log emscripten + logging filter all 1 + logging print file basename + logging print category-hex 0 + logging print category 1 + logging print thread-id 0 + logging print level 1 + logging timestamp 0 + logging color 1 +! + logging level set-all info +! logging level set-all notice +! logging level meas info +! logging level pcu info +! logging level trx notice +! logging level loop debug +! logging level abis error +! logging level lglobal info +! logging level l1p notice +! +line vty + no login +! +e1_input + e1_line 0 driver websdr + e1_line 0 port 0 + no e1_line 0 keepalive +phy 0 + osmotrx ip local 127.0.0.1 + osmotrx ip remote 127.0.0.1 + osmotrx base-port local 5800 + osmotrx base-port remote 5700 + osmotrx fn-advance 5 + osmotrx rts-advance 8 + instance 0 +bts 0 + band GSM900 + ipa unit-id 1801 0 + oml remote-ip 127.0.0.1 + rtp jitter-buffer 100 + rtp port-range 16384 17407 + paging queue-size 200 + paging lifetime 0 + gsmtap-remote-host 127.0.1.1 + gsmtap-sapi enable-all + no gsmtap-sapi pdtch + no gsmtap-sapi ptcch + min-qual-rach 50 + min-qual-norm -5 + max-ber10k-rach 1707 + smscb queue-max-length 15 + smscb queue-target-length 2 + smscb queue-hysteresis 2 + osmux + use only + batch-factor 4 + batch-size 32768 + local-ip 127.0.0.1 + local-port 6665 + trx 0 + power-ramp max-initial 0 mdBm + power-ramp step-size 8000 mdB + power-ramp step-interval 1 + ms-power-control osmo + phy 0 instance 0 + diff --git a/src/common/main.c b/src/common/main.c index 8327b57..e92f30e 100644 --- a/src/common/main.c +++ b/src/common/main.c @@ -60,7 +60,9 @@ #include <osmocom/ctrl/control_vty.h> #include <osmo-bts/oml.h>
+#ifndef ENABLE_WEBSDR static int quit = 0; +#endif /* ENABLE_WEBSDR */ static const char *config_file = "osmo-bts.cfg"; static int daemonize = 0; static int rt_prio = -1; @@ -223,6 +225,7 @@ /* FIXME: remove this once we add multi-BTS support */ struct gsm_bts *g_bts = NULL;
+#ifndef ENABLE_WEBSDR static void signal_handler(int signum) { fprintf(stderr, "signal %u received\n", signum); @@ -257,6 +260,7 @@ break; } } +#endif /* ENABLE_WEBSDR */
int bts_main(int argc, char **argv) { @@ -271,15 +275,21 @@ bts_vty_info.tall_ctx = tall_bts_ctx;
osmo_init_logging2(tall_bts_ctx, &bts_log_info); +#ifndef ENABLE_WEBSDR osmo_stats_init(tall_bts_ctx); +#endif /* ENABLE_WEBSDR */ vty_init(&bts_vty_info); ctrl_vty_init(tall_bts_ctx); osmo_cpu_sched_vty_init(tall_bts_ctx); +#ifndef ENABLE_WEBSDR rate_ctr_init(tall_bts_ctx); +#endif /* ENABLE_WEBSDR */
logging_vty_add_cmds(); osmo_talloc_vty_add_cmds(); +#ifndef ENABLE_WEBSDR osmo_stats_vty_add_cmds(); +#endif /* ENABLE_WEBSDR */ osmo_fsm_vty_add_cmds();
bts_vty_init(tall_bts_ctx); @@ -348,6 +358,7 @@ } }
+#ifndef ENABLE_WEBSDR /* Accept a GSMTAP host from VTY config, but a commandline option overrides that. */ if (gsmtap_ip != NULL) { if (g_bts->gsmtap.remote_host != NULL) { @@ -393,6 +404,7 @@ signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); osmo_init_ignore_signals(); +#endif /* ENABLE_WEBSDR */
if (bts_osmux_open(g_bts) < 0) { fprintf(stderr, "Osmux setup failed\n"); @@ -402,16 +414,21 @@ if (vty_test_mode) { /* Just select-loop without connecting to the BSC, don't exit. This allows running tests on the VTY * telnet port. */ +#ifndef ENABLE_WEBSDR while (!quit) { log_reset_context(); osmo_select_main(0); } +#else + log_reset_context(); +#endif /* ENABLE_WEBSDR */ return EXIT_SUCCESS; }
if (abis_open(g_bts, "osmo-bts") != 0) exit(1);
+#ifndef ENABLE_WEBSDR rc = phy_links_open(); if (rc < 0) { fprintf(stderr, "unable to open PHY link(s)\n"); @@ -430,6 +447,9 @@ log_reset_context(); osmo_select_main(0); } +#else + log_reset_context(); +#endif /* ENABLE_WEBSDR */
return EXIT_SUCCESS; } diff --git a/src/common/osmux.c b/src/common/osmux.c index 52885eb..0dfc64e 100644 --- a/src/common/osmux.c +++ b/src/common/osmux.c @@ -40,6 +40,14 @@ #include <osmo-bts/msg_utils.h> #include <osmo-bts/l1sap.h>
+#include "btsconfig.h" + +#ifdef ENABLE_WEBSDR +#include "osmocom/abis/websdr.h" + +static struct gsm_bts *g_bts_websdr; +#endif /* ENABLE_WEBSDR */ + /* Bitmask containing Allocated Osmux circuit ID. +7 to round up to 8 bit boundary. */ static uint8_t osmux_cid_bitmap[OSMO_BYTES_FOR_BITS(OSMUX_CID_MAX + 1)];
@@ -96,6 +104,7 @@ /* Deliver OSMUX batch to the remote end */ static void osmux_deliver_cb(struct msgb *batch_msg, void *data) { +#ifndef ENABLE_WEBSDR struct osmux_handle *handle = data; struct gsm_bts *bts = handle->bts; socklen_t dest_len; @@ -118,6 +127,12 @@ LOGP(DOSMUX, LOGL_ERROR, "osmux sendto(%s) failed: %s\n", osmo_sockaddr_to_str(&handle->rem_addr), errbuf); } +#else /* ENABLE_WEBSDR */ + int rc = ws_osmux_deliver_cb(batch_msg->data, batch_msg->len); + if (rc < 0) { + LOGP(DOSMUX, LOGL_ERROR, "osmux send() failed: %d\n", rc); + } +#endif /* ENABLE_WEBSDR */ msgb_free(batch_msg); }
@@ -128,13 +143,17 @@ struct osmux_handle *h;
llist_for_each_entry(h, &bts->osmux.osmux_handle_list, head) { +#ifndef ENABLE_WEBSDR if (osmo_sockaddr_cmp(&h->rem_addr, rem_addr) == 0) { LOGP(DOSMUX, LOGL_DEBUG, "Using existing OSMUX handle for rem_addr=%s\n", osmo_sockaddr_to_str(rem_addr)); +#endif /* ENABLE_WEBSDR */ h->refcnt++; return h; +#ifndef ENABLE_WEBSDR } +#endif /* ENABLE_WEBSDR */ }
return NULL; @@ -220,7 +239,7 @@ return h->in; }
- +#ifndef ENABLE_WEBSDR static struct msgb *osmux_recv(struct osmo_fd *ofd, struct osmo_sockaddr *addr) { struct msgb *msg; @@ -242,6 +261,7 @@
return msg; } +#endif /* ENABLE_WEBSDR */
static struct gsm_lchan *osmux_lchan_find(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr, uint8_t osmux_cid) { @@ -276,6 +296,7 @@ return NULL; }
+#ifndef ENABLE_WEBSDR static int osmux_read_fd_cb(struct osmo_fd *ofd, unsigned int what) { struct msgb *msg; @@ -302,6 +323,7 @@ msgb_free(msg); return 0; } +#endif /* ENABLE_WEBSDR */
/* Called before config file read, set defaults */ int bts_osmux_init(struct gsm_bts *bts) @@ -314,11 +336,16 @@ bts->osmux.dummy_padding = false; INIT_LLIST_HEAD(&bts->osmux.osmux_handle_list); bts->osmux.fd.fd = -1; + +#ifdef ENABLE_WEBSDR + g_bts_websdr = bts; +#endif /*ENABLE_WEBSDR*/ return 0; }
void bts_osmux_release(struct gsm_bts *bts) { +#ifndef ENABLE_WEBSDR /* bts->osmux.osmux_handle_list should end up empty when all lchans are * released/freed upon talloc_free(bts). */ /* If bts->osmux.fd.data is NULL, bts is being released/freed without @@ -326,17 +353,19 @@ * probably 0 (memzeored). Avoid accessing it if not initialized. */ if (bts->osmux.fd.fd != -1 && bts->osmux.fd.data) osmo_fd_close(&bts->osmux.fd); +#endif /* ENABLE_WEBSDR */ }
/* Called after config file read, start services */ int bts_osmux_open(struct gsm_bts *bts) { - int rc; + int rc = 0;
/* If Osmux is not enabled by VTY, don't initialize stuff */ if (bts->osmux.use == OSMUX_USAGE_OFF) return 0;
+#ifndef ENABLE_WEBSDR bts->osmux.fd.cb = osmux_read_fd_cb; bts->osmux.fd.data = bts; rc = osmo_sock_init2_ofd(&bts->osmux.fd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, @@ -352,6 +381,7 @@ LOGP(DOSMUX, LOGL_INFO, "Osmux socket listening on %s:%u\n", bts->osmux.local_addr ? : "*", bts->osmux.local_port); +#endif /* ENABLE_WEBSDR */
osmo_bts_set_feature(bts->features, BTS_FEAT_OSMUX); return rc; @@ -543,3 +573,38 @@ msgb_free(msg); return 0; } + +#ifdef ENABLE_WEBSDR +int ws_osmux_push_raw_data(const char* data, size_t sz) +{ + struct gsm_bts *bts = g_bts_websdr; + struct osmux_hdr *osmuxh; + struct msgb *msg; + + if (sz > 4096) + return -EINVAL; + if (!bts) + return -EINVAL; + + msg = msgb_alloc(4096, "OSMUX"); /* TODO: pool? */ + if (!msg) { + LOGP(DOSMUX, LOGL_ERROR, "cannot allocate message\n"); + return -ENOMEM; + } + memcpy(msg->data, data, sz); + msgb_put(msg, sz); + + while ((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct gsm_lchan *lchan = osmux_lchan_find(bts, NULL, osmuxh->circuit_id); + if (!lchan) { + LOGP(DOSMUX, LOGL_DEBUG, + "Cannot find lchan for CID=%d\n", osmuxh->circuit_id); + + continue; + } + osmux_xfrm_output_sched(lchan->abis_ip.osmux.out, osmuxh); + } + msgb_free(msg); + return 0; +} +#endif /* ENABLE_WEBSDR */ diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am index 63c00fe..83dd233 100644 --- a/src/osmo-bts-trx/Makefile.am +++ b/src/osmo-bts-trx/Makefile.am @@ -38,9 +38,11 @@ trx_provision_fsm.h \ $(NULL)
-bin_PROGRAMS = osmo-bts-trx +# Build shared objects into a convenience lib to avoid compiling the same +# sources twice (once for a libtool library and once for a program). +noinst_LIBRARIES = libosmo_bts_trx.a
-osmo_bts_trx_SOURCES = \ +libosmo_bts_trx_a_SOURCES = \ main.c \ trx_if.c \ l1_if.c \ @@ -57,7 +59,12 @@ probes.d \ $(NULL)
+if !ENABLE_WEBSDR + +bin_PROGRAMS = osmo-bts-trx + osmo_bts_trx_LDADD = \ + libosmo_bts_trx.la \ $(top_builddir)/src/common/libl1sched.a \ $(top_builddir)/src/common/libbts.a \ $(LDADD) \ @@ -73,3 +80,57 @@ BUILT_SOURCES = probes.h probes.lo osmo_bts_trx_LDADD += probes.lo endif + +else + +bin_PROGRAMS = osmo-bts-trx-websdr.js + +if HAVE_EMSCRIPTEN +AM_CFLAGS += \ + -O3 \ + -flto \ + -Wno-unused-main \ + -Wno-limited-postlink-optimizations \ + $(LIBOSMOTRX_CFLAGS) \ + $(NULL) + +osmo_bts_trx_websdr_js_LDFLAGS = \ + -sASSERTIONS=1 \ + -sMODULARIZE=1 \ + -sEXPORT_ES6=1 \ + -sASYNCIFY=1 \ + -sWASM_BIGINT=1 \ + -sENVIRONMENT="worker" \ + -sLLD_REPORT_UNDEFINED=1 \ + -sSINGLE_FILE=0 \ + -sEXPORTED_FUNCTIONS=[_osmobts_init,_osmobts_apply,_osmobts_getiqtx_short_vector,_osmobts_push_rx_short_vector,_ws_osmux_push_raw_data,_ws_ipa_push_raw_data,_on_sched_timer,_malloc,_free] \ + -sEXPORTED_RUNTIME_METHODS=[HEAPF32,HEAPU32,HEAP32,HEAPU16,HEAP16,HEAPU8,HEAP8,ccall,cwrap,stringToAscii,AsciiToString] \ + -sINITIAL_MEMORY=256MB \ + -sFORCE_FILESYSTEM \ + --embed-file ../../doc/examples/trx/osmo-bts-trx-websdr.cfg@osmo-bts-trx.cfg \ + $(NULL) + +nodist_noinst_DATA = osmo-bts-trx-websdr.wasm + +osmo-bts-trx-websdr.wasm: osmo-bts-trx-websdr.js + @test -f $@ + +install-exec-hook: + $(INSTALL_DATA) osmo-bts-trx-websdr.wasm \ + $(DESTDIR)$(bindir)/ + +endif + +osmo_bts_trx_websdr_js_SOURCES = \ + osmo-bts-trx-websdr.c \ + $(NULL) + +osmo_bts_trx_websdr_js_LDADD = \ + libosmo_bts_trx.a \ + $(top_builddir)/src/common/libl1sched.a \ + $(top_builddir)/src/common/libbts.a \ + $(LDADD) \ + $(LIBOSMOTRX_LIBS) \ + $(NULL) + +endif diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c index c870dfb..3685635 100644 --- a/src/osmo-bts-trx/main.c +++ b/src/osmo-bts-trx/main.c @@ -60,6 +60,9 @@
#include "l1_if.h" #include "trx_if.h" +#ifdef ENABLE_WEBSDR +#include "osmo-bts-trx-websdr.h" +#endif /* ENABLE_WEBSDR */
static const struct rate_ctr_desc btstrx_ctr_desc[] = { [BTSTRX_CTR_SCHED_DL_MISS_FN] = { @@ -222,6 +225,14 @@ l1h = trx_l1h_alloc(tall_bts_ctx, pinst); pinst->u.osmotrx.hdl = l1h;
+#ifdef ENABLE_WEBSDR + int res = apitrx_init(l1h); + if (res < 0) { + fprintf(stderr, "API TRX initialization failed\n"); + exit(1); + } +#endif /* ENABLE_WEBSDR */ + l1h->config.forced_max_power_red = -1; }
diff --git a/src/osmo-bts-trx/osmo-bts-trx-websdr.c b/src/osmo-bts-trx/osmo-bts-trx-websdr.c new file mode 100644 index 0000000..0bf6bd8 --- /dev/null +++ b/src/osmo-bts-trx/osmo-bts-trx-websdr.c @@ -0,0 +1,260 @@ +/* Osmo BTS TRX WebSDR API implementation + * + * Copyright (C) 2026 Wavelet Lab info@wavelet-lab.com + * + * All Rights Reserved + * + * 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 <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/abis.h> +#include <osmo-bts/logging.h> + +#include <osmo-trx-websdr.h> +#include <trx_stats_json.h> +#include <trx_if.h> +#include <assert.h> + +#include "osmo-bts-trx-websdr.h" +#include "stats_json.h" + + +#define MAX_BUFS 32 + +#define CHUNK 625 +#define CHUNK_COUNT 8 +#define BURST_DURATION (CHUNK * CHUNK_COUNT) + +unsigned modulateBits(const uint8_t *bits, unsigned bitlen, int16_t *outbuf); + +extern struct gsm_bts *g_bts; + +uint64_t sdr_send_ts = 0; +uint64_t g_last_ts = 0; + +unsigned wptr = 0; +unsigned rptr = 0; +unsigned total_tx_late = 0; +unsigned start_fn = ~0u; + +int16_t sdr_send_buffer[ MAX_BUFS * BURST_DURATION * 2 ]; +uint64_t sdr_send_bts[ MAX_BUFS ]; + +static struct trx_l1h *g_l1h = NULL; + + +int apply_changes(const char *band, unsigned arfcn, const char *ipaccess_unitid, unsigned osmux_port) +{ + enum gsm_band b = gsm_band_parse(band); + long ipaccess_id = strtol(ipaccess_unitid, NULL, 10); + + if (b == -EINVAL || ipaccess_id <= 0 || osmux_port <= 0) + return -EINVAL; + + g_bts->band = b; + g_bts->ip_access.site_id = ipaccess_id; + g_bts->ip_access.bts_id = 0; + g_bts->osmux.local_port = osmux_port; + + abis_update(g_bts->ip_access.site_id, g_bts->ip_access.bts_id); + return 0; +} + +bool osmobts_init(char *file) +{ + char *params[] = { + "./webusb-bts", + "-c", + file, + }; + const size_t num_params = sizeof(params) / sizeof(params[0]); + + int res = bts_main(num_params, params); + LOGP(DTRX, LOGL_INFO, "Started Osmo BTS WebSDR API with config file: '%s', returned %d\n", file, res); + + if (res != EXIT_SUCCESS) + return false; + + return true; +} + +bool osmobts_apply(const char *band, unsigned arfcn, const char *ipaccess_unitid, unsigned osmux_port) +{ + int res = apply_changes(band, arfcn, ipaccess_unitid, osmux_port); + if (res) + return false; + + LOGP(DTRX, LOGL_INFO, "bts_apply config `%s:%d:%s:%d`\n", band, arfcn, ipaccess_unitid, osmux_port); + + return true; +} + +int apitrx_init(struct trx_l1h *l1h) +{ + int res; + + res = osmotrxlib_init(); + if (res) + return res; + + g_l1h = l1h; + + return 0; +} + +int apitrx_cmd_call(const char *command, char *response, size_t response_size) +{ + int res = osmotrxlib_process_command(command, response, response_size); + LOGP(DTRX, LOGL_INFO, "Got CMD `%s` => res:%d, response: `%s`\n", + command, res, response ? response : "NULL"); + + return res; +} + + +int apitrx_tx_call(unsigned phyid, char *pdu_buffer, size_t pdu_len) +{ + int res = osmotrxlib_put_tx_burst(pdu_buffer, pdu_len); + if (res < 0) { + LOGP(DTRX, LOGL_ERROR, "osmotrxlib_put_tx_burst failed, res = %d\n", res); + return res; + } + + res = osmotrxlib_get_tx_short_vector(); + + return res; +} + +void osmotrxlib_on_clock_data(unsigned clock) +{ + if (start_fn == ~0u) { + start_fn = clock; + } + fprintf(stderr, "CLOCK IND %u (start_fn=%u)\n", clock, start_fn); + LOGP(DTRX, LOGL_INFO, "CLOCK IND %d\n", clock); + apibts_clock_ind(g_l1h, clock); +} + +int osmobts_push_rx_short_vector(short *buffers, int samples, int underrun, uint64_t ts, unsigned skipsamples) +{ + char burst_data[512]; + int res; + + g_last_ts = ts; + + res = osmotrxlib_push_rx_short_vector(buffers, samples, underrun, ts, skipsamples); + if (res < 0) { + LOGP(DTRX, LOGL_ERROR, "osmotrxlib_push_rx_short_vector failed res = %d\n", res); + return res; + } + + for (unsigned i = 0; i < 4; i++) { + res = osmotrxlib_get_rx_burst(burst_data, sizeof(burst_data)); + if (res < 0) { + LOGP(DTRX, LOGL_ERROR, "osmotrxlib_get_rx_burst failed res = %d burst = %d\n", res, i); + continue; + } + if (res == 0) { + LOGP(DTRX, LOGL_INFO, "osmotrxlib_get_rx_burst i=%d nodata\n", i); + continue; + } + + res = trx_data_read(g_l1h, (uint8_t *)burst_data, res); + if (res < 0) { + LOGP(DTRX, LOGL_ERROR, "trx_data_read failed res = %d burst = %d\n", res, i); + continue; + } + } + + return 0; +} + +int osmobts_getiqtx_short_vector(uint64_t *ts, int16_t **bptr, unsigned *ptotlate) +{ + if (wptr == rptr) + return -ENODATA; + + unsigned idx = rptr % MAX_BUFS; + + *ts = sdr_send_bts[idx]; + *bptr = sdr_send_buffer + idx * BURST_DURATION * 2; + *ptotlate = total_tx_late; + rptr++; + + return 0; +} + +int apitrx_tx_call_fn(unsigned phyid, const struct burstinfo *pbi) +{ + /* Ignore slot information and types for now */ + unsigned boff = 0; + unsigned blen; + unsigned idx = wptr % MAX_BUFS; + int16_t *outptr; + assert(wptr < rptr + MAX_BUFS); + + if (sdr_send_ts == 0) { + /* Start_fn is actually RX_FN + 2, so + * Delta is TX_FN - (IND_FN - 2), which is TX_FN - RX_FN! + */ + sdr_send_ts = (pbi->fn - start_fn + 2) * BURST_DURATION; + } + + /* warm up things */ + if (sdr_send_ts < 100 * BURST_DURATION) { + sdr_send_ts += BURST_DURATION; + return 1; + } + + if (g_last_ts + 256 > sdr_send_ts) { + total_tx_late++; + sdr_send_ts += BURST_DURATION; + return 1; + } + + outptr = sdr_send_buffer + idx * BURST_DURATION * 2; + + /* Iterate over TN */ + for (unsigned i = 0; i < CHUNK_COUNT; i++) { + const struct burstinfo *pb = &pbi[i]; + blen = modulateBits(pb->burst, pb->len, outptr + boff); + + assert(blen == 612); + assert(pb->tn == i); + + boff += CHUNK << 1; + } + + sdr_send_bts[idx] = sdr_send_ts; + + wptr++; + sdr_send_ts += BURST_DURATION; + + return 0; +} diff --git a/src/osmo-bts-trx/osmo-bts-trx-websdr.h b/src/osmo-bts-trx/osmo-bts-trx-websdr.h new file mode 100644 index 0000000..a99c111 --- /dev/null +++ b/src/osmo-bts-trx/osmo-bts-trx-websdr.h @@ -0,0 +1,48 @@ +/* Osmo BTS TRX WebSDR API + * + * Copyright (C) 2026 Wavelet Lab info@wavelet-lab.com + * + * All Rights Reserved + * + * 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/. + * + */ + +#ifndef OSMO_BTS_TRX_WEBSDR_H +#define OSMO_BTS_TRX_WEBSDR_H + +#include <stdint.h> +#include <stddef.h> + +#include "l1_if.h" + +struct burstinfo { + uint32_t fn; + uint8_t tn; + uint8_t att; + uint16_t len; + const uint8_t *burst; +}; + +/* Call to the TRX */ +int apitrx_init(struct trx_l1h *l1h); + +int apitrx_cmd_call(const char *command, char *response, size_t response_size); +int apitrx_tx_call(unsigned phyid, char *pdu_buffer, size_t pdu_len); +int apitrx_tx_call_fn(unsigned phyid, const struct burstinfo *pbi); + +/* Called from TRX */ +int apibts_clock_ind(struct trx_l1h *l1h, unsigned fn); + +#endif /* OSMO_BTS_TRX_WEBSDR_H */ \ No newline at end of file diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c index 277d4d5..01b5e83 100644 --- a/src/osmo-bts-trx/scheduler_trx.c +++ b/src/osmo-bts-trx/scheduler_trx.c @@ -54,6 +54,13 @@
#include "btsconfig.h"
+#ifdef ENABLE_WEBSDR +#include "osmo-bts-trx-websdr.h" +#ifdef __EMSCRIPTEN__ +#include <emscripten.h> +#endif /* __EMSCRIPTEN__ */ +#endif /* ENABLE_WEBSDR */ + #ifdef HAVE_SYSTEMTAP /* include the generated probes header and put markers in code */ #include "probes.h" @@ -69,6 +76,24 @@ #define SCHED_FH_PARAMS_VALS(ts) \ (ts)->hopping.hsn, (ts)->hopping.maio, (ts)->hopping.arfcn_num
+#ifdef ENABLE_WEBSDR +static struct gsm_bts *g_bts_websdr; + +#ifdef __EMSCRIPTEN__ +EM_JS(void, start_timer_interval_wrapper, (uint64_t interval), { + start_timer_interval(interval); +}); +#endif /* __EMSCRIPTEN__ */ + +static void start_timer_interval(struct gsm_bts *bts, uint64_t interval) +{ + g_bts_websdr = bts; +#ifdef __EMSCRIPTEN__ + start_timer_interval_wrapper(interval); +#endif /* __EMSCRIPTEN__ */ +} +#endif /* ENABLE_WEBSDR */ + static void lchan_report_interf_meas(const struct gsm_lchan *lchan) { const struct gsm_bts_trx_ts *ts = lchan->ts; @@ -210,6 +235,8 @@ br->att = ts->c0_power_red_db; } } + +#ifndef ENABLE_WEBSDR static void trx_sched_submit_bursts(struct phy_instance *pinst) { unsigned int tn; @@ -225,6 +252,26 @@ trx_if_send_burst(l1h, NULL); }
+#else /* ENABLE_WEBSDR */ + +static void trx_sched_submit_bursts(struct phy_instance *pinst) +{ + unsigned int tn; + struct burstinfo bi[TRX_NR_TS]; + + for (tn = 0; tn < TRX_NR_TS; tn++) { + const struct trx_dl_burst_req *br; + br = &pinst->u.osmotrx.br[tn]; + bi[tn].fn = br->fn; + bi[tn].tn = br->tn; + bi[tn].att = br->att; + bi[tn].len = br->burst_len; + bi[tn].burst = br->burst; + } + + apitrx_tx_call_fn(0, bi); +} +#endif /* ENABLE_WEBSDR */
static void bts_sched_flush_buffers(struct gsm_bts *bts) { @@ -451,6 +498,7 @@ return 0; }
+#ifndef ENABLE_WEBSDR /*! this is the timerfd-callback firing for every FN to be processed */ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) { @@ -492,10 +540,12 @@ bts_shutdown(bts, "No clock since TRX was started"); return -1; } +#endif /* ENABLE_WEBSDR */
/*! \brief PHY informs us clock indications should start to be received */ int trx_sched_clock_started(struct gsm_bts *bts) { +#ifndef ENABLE_WEBSDR struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; const struct timespec it_val = {3, 0}; @@ -511,17 +561,20 @@ */ osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_start_noclockind_to_cb, bts); osmo_timerfd_schedule(&tcs->fn_timer_ofd, &it_val, &it_intval); +#endif /* ENABLE_WEBSDR */ return 0; }
/*! \brief PHY informs us no more clock indications should be received anymore */ int trx_sched_clock_stopped(struct gsm_bts *bts) { +#ifndef ENABLE_WEBSDR struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; struct osmo_trx_clock_state *tcs = &bts_trx->clk_s;
LOGP(DL1C, LOGL_NOTICE, "GSM clock stopped\n"); osmo_fd_close(&tcs->fn_timer_ofd); +#endif /* ENABLE_WEBSDR */
return 0; } @@ -531,9 +584,14 @@ static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs, struct timespec *tv_now, const struct timespec *interval, uint32_t fn) { +#ifndef ENABLE_WEBSDR /* schedule first FN clock timer */ osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_fn_timer_cb, bts); osmo_timerfd_schedule(&tcs->fn_timer_ofd, NULL, interval); +#else /* ENABLE_WEBSDR */ + uint64_t s = interval->tv_sec * 1000000000 + interval->tv_nsec; + start_timer_interval(bts, s); +#endif /* ENABLE_WEBSDR */
tcs->last_fn_timer.fn = fn; tcs->last_fn_timer.tv = *tv_now; @@ -607,7 +665,9 @@ normalize_timespec(&first); LOGP(DL1C, LOGL_NOTICE, "We were %d FN faster than TRX, compensating\n", -elapsed_fn); /* set time to the time our next FN has to be transmitted */ +#ifndef ENABLE_WEBSDR osmo_timerfd_schedule(&tcs->fn_timer_ofd, &first, &interval); +#endif /* ENABLE_WEBSDR */ return 0; }
@@ -724,3 +784,16 @@ pos = (current + hist_size - shift) % hist_size; return chan_state->meas.buf[pos].fn; } + +#ifdef ENABLE_WEBSDR +void on_sched_timer(unsigned frames) { + struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)g_bts_websdr->model_priv; + struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; + + tcs->fn_without_clock_ind = 0; + + osmo_timers_prepare(); + trx_fn_timer_process(g_bts_websdr, frames); + osmo_timers_update(); +} +#endif /* ENABLE_WEBSDR */ \ No newline at end of file diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c index 9844b98..69de93a 100644 --- a/src/osmo-bts-trx/trx_if.c +++ b/src/osmo-bts-trx/trx_if.c @@ -52,6 +52,10 @@ #include "trx_provision_fsm.h"
#include "btsconfig.h" +#ifdef ENABLE_WEBSDR +#include <osmo-trx-websdr.h> +#include "osmo-bts-trx-websdr.h" +#endif /* ENABLE_WEBSDR */
#ifdef HAVE_SYSTEMTAP /* include the generated probes header and put markers in code */ @@ -89,6 +93,7 @@ return 0; }
+#ifndef ENABLE_WEBSDR /* get clock from clock socket */ static void trx_clk_read_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) { @@ -166,6 +171,28 @@ return rc; }
+#else /* ENABLE_WEBSDR */ + +static int trx_ctrl_parse_rsp(struct trx_l1h *l1h, const char *buf, size_t buf_len); + +static int trx_ctrl_submit_msg(struct trx_l1h *l1h, struct msgb *msg, const char *buf) +{ + int rc; + char response[101]; /* sizeof ctrl_msg.data from Transceiver */ + + rc = osmotrxlib_process_command(buf, response, sizeof(response)); + if (rc < 0) { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "osmotrxlib_process_command(%s) failed on TRXC with rc=%d\n", buf, rc); + msgb_free(msg); + } else { + trx_ctrl_parse_rsp(l1h, response, sizeof(response)); + } + + return rc; +} +#endif /* ENABLE_WEBSDR */ + /* send first ctrl message and start timer */ static void trx_ctrl_send(struct trx_l1h *l1h) { @@ -188,6 +215,7 @@ trx_ctrl_submit_msg(l1h, msg, buf); }
+#ifndef ENABLE_WEBSDR /* send first ctrl message and start timer */ static void trx_ctrl_timer_cb(void *data) { @@ -212,6 +240,7 @@ /* initialize ctrl queue */ INIT_LLIST_HEAD(&l1h->trx_ctrl_list); } +#endif /* ENABLE_WEBSDR */
/*! Send a new TRX control command. * \param[inout] l1h TRX Layer1 handle to which to send command @@ -276,6 +305,28 @@ return 0; }
+#ifdef ENABLE_WEBSDR +void trx_if_init(struct trx_l1h *l1h) +{ + l1h->trx_ctrl_timer.cb = NULL; + l1h->trx_ctrl_timer.data = NULL; + l1h->trx_ctrl_iofd = NULL; + l1h->trx_data_iofd = NULL; + + /* initialize ctrl queue */ + INIT_LLIST_HEAD(&l1h->trx_ctrl_list); +} + +/* get clock from physical instance */ +int apibts_clock_ind(struct trx_l1h *l1h, unsigned fn) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + + return trx_clk_phy(plink, pinst, fn); +} +#endif /* ENABLE_WEBSDR */ + /*! Send "POWEROFF" command to TRX */ int trx_if_cmd_poweroff(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb) { @@ -758,6 +809,7 @@
}
+#ifndef ENABLE_WEBSDR /*! Get + parse response from TRX ctrl socket */ static void trx_ctrl_read_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) { @@ -778,6 +830,7 @@ /* libosmocore before change-id I0c071a29e508884bac331ada5e510bbfcf440bbf requires * write call-back even if we don't care about it */ } +#endif /* ENABLE_WEBSDR */
/* * TRX burst data socket @@ -1124,6 +1177,7 @@ return 0; }
+#ifndef ENABLE_WEBSDR /* TRXD socket read callback. * Called when a TRXD datagram is received on the IOFD. * If `res` <= 0 an error is logged; otherwise the payload is passed @@ -1172,6 +1226,26 @@ return rc; }
+#else /* ENABLE_WEBSDR */ + +static int trx_data_submit_msg(struct trx_l1h *l1h, struct msgb *msg) +{ + int rc; + + rc = apitrx_tx_call(0, (char *)msgb_data(msg), msgb_length(msg)); + if (OSMO_UNLIKELY(rc < 0)) { + char errbuf[256]; + strerror_r(errno, errbuf, sizeof(errbuf)); + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "apitrx_tx_call() failed on TRXD with rc=%d (%s)\n", + rc, errbuf); + msgb_free(msg); + } + + return rc; +} +#endif /* ENABLE_WEBSDR */ + /*! Send burst data for given FN/timeslot to TRX * \param[inout] l1h TRX Layer1 handle referring to TX * \param[in] br Downlink burst request structure @@ -1306,6 +1380,7 @@ l1h->trx_data_iofd = NULL; }
+#ifndef ENABLE_WEBSDR /*! compute UDP port number used for TRX protocol */ static uint16_t compute_port(struct phy_instance *pinst, bool remote, bool is_data) { @@ -1393,6 +1468,14 @@ return rc; }
+#else /* ENABLE_WEBSDR */ + +static int trx_if_open(struct trx_l1h *l1h) +{ + return 0; +} +#endif /* ENABLE_WEBSDR */ + /*! close the control + burst data sockets for one phy_instance */ static void trx_phy_inst_close(struct phy_instance *pinst) { @@ -1431,11 +1514,14 @@ int bts_model_phy_link_open(struct phy_link *plink) { struct phy_instance *pinst; +#ifndef ENABLE_WEBSDR char sock_name_buf[OSMO_SOCK_NAME_MAXLEN] = {}; int fd; +#endif /* ENABLE_WEBSDR */
phy_link_state_set(plink, PHY_LINK_CONNECTING);
+#ifndef ENABLE_WEBSDR /* open the shared/common clock socket */ fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, plink->u.osmotrx.local_ip, @@ -1455,6 +1541,7 @@ goto cleanup; osmo_iofd_set_alloc_info(plink->u.osmotrx.trx_clk_iofd, TRXC_MSG_BUF_SIZE, 0); osmo_iofd_register(plink->u.osmotrx.trx_clk_iofd, -1); +#endif /* ENABLE_WEBSDR */
/* open the individual instances with their ctrl+data sockets */ llist_for_each_entry(pinst, &plink->instances, list) { @@ -1474,7 +1561,9 @@ pinst->u.osmotrx.hdl = NULL; } } +#ifndef ENABLE_WEBSDR osmo_iofd_free(plink->u.osmotrx.trx_clk_iofd); +#endif /* ENABLE_WEBSDR */ plink->u.osmotrx.trx_clk_iofd = NULL; return -1; } diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c index b806dc6..6160310 100644 --- a/src/osmo-bts-trx/trx_vty.c +++ b/src/osmo-bts-trx/trx_vty.c @@ -62,9 +62,13 @@ llist_for_each_entry(trx, &g_bts->trx_list, list) { struct phy_instance *pinst = trx_phy_instance(trx); struct phy_link *plink = pinst->phy_link; +#ifndef ENABLE_WEBSDR const char *sname = plink->u.osmotrx.trx_clk_iofd ? osmo_iofd_get_name(plink->u.osmotrx.trx_clk_iofd) : NULL; +#else /* ENABLE_WEBSDR */ + const char *sname = "websdr"; +#endif /* ENABLE_WEBSDR */ l1h = pinst->u.osmotrx.hdl; vty_out(vty, "TRX %d %s%s", trx->nr, sname ? sname : "", VTY_NEWLINE); vty_out(vty, " %s%s",