/* Osmo BTS TRX WebSDR API implementation * * Copyright (C) 2026 Wavelet Lab * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_bts_info(g_bts->ip_access.site_id, g_bts->ip_access.bts_id); return 0; } bool osmobts_init(const char *file) { const char *params[] = { "./webusb-bts", "-c", file, }; const size_t num_params = ARRAY_SIZE(params); int res = bts_main(num_params, (char **)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 osmobts_get_stats(const char *group, char *buf, unsigned buflen) { if (!strncmp(group, "stats", 5)) return stats_to_json(buf, buflen); else if (!strncmp(group, "rate-counters", 13)) return rate_counters_to_json(buf, buflen); else if (!strncmp(group, "bts", 3)) return bts_to_json(buf, buflen); else if (!strncmp(group, "trx", 3)) return trx_to_json(buf, buflen); else if (!strncmp(group, "transceiver", 11)) return transceiver_to_json(buf, buflen); else if (!strncmp(group, "websdr", 6)) return websdr_to_json(buf, buflen); return -EINVAL; } 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_get_tx_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; }