Timur Davydov has uploaded this change for review.
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",
To view, visit change 42703. To unsubscribe, or for help writing mail filters, visit settings.