fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmo-trx/+/43604?usp=email )
(
3 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: proxy: add TDMA clock generator and basic TRXC handler ......................................................................
proxy: add TDMA clock generator and basic TRXC handler
Add clck_gen: a single, process-wide timerfd-driven TDMA clock, sending "IND CLOCK" every 102 frames to the clock socket of each currently powered-on endpoint. The clock starts the moment any endpoint becomes powered and stops once none remain.
Add ctrl_cmd implementing osmo_trx_ep_rx_ctrl_msg(). This module currently handles POWERON/POWEROFF and RXTUNE/TXTUNE, and simply ACKs all unknown commands.
Change-Id: I8c0757f15961bf0574f1305a6a8836853b66afcf Related: OS#6672 --- M proxy/Makefile.am A proxy/include/osmocom/proxy/clck_gen.h M proxy/include/osmocom/proxy/trx.h M proxy/src/Makefile.am A proxy/src/clck_gen.c A proxy/src/ctrl_cmd.c M proxy/src/main.c M proxy/src/trx.c M proxy/src/vty.c 9 files changed, 340 insertions(+), 4 deletions(-)
Approvals: pespin: Looks good to me, approved laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified
diff --git a/proxy/Makefile.am b/proxy/Makefile.am index 23a1d3a..e99dd63 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -3,6 +3,7 @@ $(NULL)
noinst_HEADERS = \ + include/osmocom/proxy/clck_gen.h \ include/osmocom/proxy/logging.h \ include/osmocom/proxy/proxy.h \ include/osmocom/proxy/trx.h \ diff --git a/proxy/include/osmocom/proxy/clck_gen.h b/proxy/include/osmocom/proxy/clck_gen.h new file mode 100644 index 0000000..dae2a2d --- /dev/null +++ b/proxy/include/osmocom/proxy/clck_gen.h @@ -0,0 +1,5 @@ +#pragma once + +int clck_gen_init(void); + +void clck_gen_trx_list_updated(void); diff --git a/proxy/include/osmocom/proxy/trx.h b/proxy/include/osmocom/proxy/trx.h index e687825..52d2e6a 100644 --- a/proxy/include/osmocom/proxy/trx.h +++ b/proxy/include/osmocom/proxy/trx.h @@ -1,24 +1,44 @@ #pragma once
+#include <stdint.h> +#include <stdbool.h> + #include <osmocom/core/linuxlist.h> #include <osmocom/core/logging.h>
struct osmo_trx_ep; struct proxy_ctx;
+/*! Per-channel state: each channel is conceptually its own (child) + * transceiver with an independent Rx/Tx frequency, sharing the endpoint's + * power state and clock. */ +struct proxy_trx_chan { + uint32_t rx_freq; /*!< Rx frequency in Hz, 0 if not (yet) tuned */ + uint32_t tx_freq; /*!< Tx frequency in Hz, 0 if not (yet) tuned */ +}; + /*! One virtual transceiver endpoint */ struct proxy_trx { struct llist_head list; char *name; struct osmo_trx_ep *ep; + bool powered; /*!< POWERON/POWEROFF applies to all channels at once */ + unsigned int num_chans; /*!< mirrors osmo_trx_ep_get_num_chans(ep) */ + struct proxy_trx_chan *chans; /*!< array of num_chans entries, allocated on open */ };
#define LOGP_TRX(trx, ss, level, fmt, args...) \ LOGP(ss, level, "(trx=%s) " fmt, (trx)->name, ##args)
+#define LOGP_TRXCH(trx, chan, ss, level, fmt, args...) \ + LOGP(ss, level, "(trx=%s, chan=%u) " fmt, (trx)->name, chan, ##args) + struct proxy_trx *proxy_trx_find(struct proxy_ctx *proxy, const char *name); struct proxy_trx *proxy_trx_alloc(struct proxy_ctx *proxy, const char *name); void proxy_trx_free(struct proxy_trx *trx);
int proxy_trx_open(struct proxy_trx *trx); void proxy_trx_close(struct proxy_trx *trx); + +int proxy_trx_set_num_chans(struct proxy_trx *trx, unsigned int num_chans); +void proxy_trx_set_power(struct proxy_trx *trx, bool on); diff --git a/proxy/src/Makefile.am b/proxy/src/Makefile.am index 0350474..4be255f 100644 --- a/proxy/src/Makefile.am +++ b/proxy/src/Makefile.am @@ -18,6 +18,8 @@ proxy.c \ vty.c \ trx.c \ + clck_gen.c \ + ctrl_cmd.c \ logging.c \ $(NULL)
diff --git a/proxy/src/clck_gen.c b/proxy/src/clck_gen.c new file mode 100644 index 0000000..c485711 --- /dev/null +++ b/proxy/src/clck_gen.c @@ -0,0 +1,135 @@ +/*! \file src/clck_gen.c + * osmo-trx-proxy: shared TDMA clock generator. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * 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 <unistd.h> +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/trx/ep.h> + +#include <osmocom/proxy/proxy.h> +#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/clck_gen.h> +#include <osmocom/proxy/logging.h> + +/*! Send "IND CLOCK" every N frames (matches fake_trx.py's CLCKGen default) */ +#define CLCK_GEN_IND_PERIOD 102 + +struct clck_gen { + struct osmo_fd timerfd; + uint32_t fn; + bool running; +}; + +static struct clck_gen g_clck_gen; + +static int clck_gen_timer_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct clck_gen *gen = ofd->data; + uint64_t expire_count; + + if (read(ofd->fd, &expire_count, sizeof(expire_count)) != sizeof(expire_count)) + return 0; + + while (expire_count-- > 0) { + const struct proxy_trx *trx; + + if (gen->fn % CLCK_GEN_IND_PERIOD == 0) { + llist_for_each_entry(trx, &g_proxy_ctx->trx_list, list) { + if (!trx->powered) + continue; + if (!osmo_trx_ep_get_clock_socket(trx->ep)) + continue; + LOGP_TRX(trx, DTRXC, LOGL_DEBUG, + "Tx CLCK.ind (fn=%u)\n", gen->fn); + osmo_trx_ep_send_clck_ind(trx->ep, gen->fn); + } + } + + /* TODO: drive per-frame burst forwarding (burst_queue/burst_fwd) */ + + GSM_TDMA_FN_INC(gen->fn); + } + + return 0; +} + +static void clck_gen_start(struct clck_gen *gen) +{ + const struct timespec first = { .tv_nsec = GSM_TDMA_FN_DURATION_nS }; + const struct timespec interval = { .tv_nsec = GSM_TDMA_FN_DURATION_nS }; + + if (gen->running) + return; + + gen->fn = 0; + if (osmo_timerfd_schedule(&gen->timerfd, &first, &interval) < 0) { + LOGP(DTRXC, LOGL_ERROR, "Failed to start the TDMA clock generator\n"); + return; + } + + gen->running = true; + LOGP(DTRXC, LOGL_NOTICE, "TDMA clock generator started\n"); +} + +static void clck_gen_stop(struct clck_gen *gen) +{ + if (!gen->running) + return; + + osmo_timerfd_disable(&gen->timerfd); + gen->running = false; + LOGP(DTRXC, LOGL_NOTICE, "TDMA clock generator stopped\n"); +} + +/*! Re-evaluate g_proxy_ctx->trx_list and (re)start or stop the (single, + * process-wide) TDMA clock generator: running iff at least one endpoint is + * currently powered on. Call this after changing any struct proxy_trx's + * ->powered field. */ +void clck_gen_trx_list_updated(void) +{ + bool any_active = false; + struct proxy_trx *trx; + + llist_for_each_entry(trx, &g_proxy_ctx->trx_list, list) { + any_active |= trx->powered; + } + + if (any_active) + clck_gen_start(&g_clck_gen); + else + clck_gen_stop(&g_clck_gen); +} + +int clck_gen_init(void) +{ + g_clck_gen.timerfd.fd = -1; + return osmo_timerfd_setup(&g_clck_gen.timerfd, + &clck_gen_timer_cb, + &g_clck_gen); +} diff --git a/proxy/src/ctrl_cmd.c b/proxy/src/ctrl_cmd.c new file mode 100644 index 0000000..281cc13 --- /dev/null +++ b/proxy/src/ctrl_cmd.c @@ -0,0 +1,130 @@ +/*! \file src/ctrl_cmd.c + * osmo-trx-proxy: TRXC command handling. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * 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 <stdio.h> +#include <string.h> + +#include <osmocom/trx/ep.h> +#include <osmocom/trx/trxc.h> + +#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/logging.h> + +static void ctrl_cmd_poweron(struct proxy_trx *trx, struct osmo_trxc_msg *rsp) +{ + if (trx->powered) { + LOGP_TRX(trx, DTRXC, LOGL_ERROR, + "Rx POWERON: already powered on\n"); + rsp->status = 1; + return; + } + + for (unsigned int chan = 0; chan < trx->num_chans; chan++) { + if (trx->chans[chan].rx_freq == 0 || trx->chans[chan].tx_freq == 0) { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "Rx POWERON: Rx/Tx frequency not (yet) set\n"); + rsp->status = 1; + return; + } + } + + proxy_trx_set_power(trx, true); +} + +static void ctrl_cmd_poweroff(struct proxy_trx *trx, struct osmo_trxc_msg *rsp) +{ + proxy_trx_set_power(trx, false); +} + +static void ctrl_cmd_rxtune(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + unsigned int freq_khz = 0; + + if (osmo_trxc_msg_params_scan(cmd, "%u", &freq_khz) != 1) { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse Rx frequency: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + trx->chans[chan].rx_freq = freq_khz * 1000; + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, + "Rx frequency set to %u kHz\n", freq_khz); + snprintf(rsp->params, sizeof(rsp->params), "%u", freq_khz); +} + +static void ctrl_cmd_txtune(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + unsigned int freq_khz = 0; + + if (osmo_trxc_msg_params_scan(cmd, "%u", &freq_khz) != 1) { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse Tx frequency: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + trx->chans[chan].tx_freq = freq_khz * 1000; + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, + "Tx frequency set to %u kHz\n", freq_khz); + snprintf(rsp->params, sizeof(rsp->params), "%u", freq_khz); +} + +void osmo_trx_ep_rx_ctrl_msg(struct osmo_trx_ep *ep, unsigned int chan, + const struct osmo_trxc_msg *cmd) +{ + struct proxy_trx *trx = osmo_trx_ep_get_priv(ep); + struct osmo_trxc_msg rsp = *cmd; + + LOGP_TRXCH(trx, chan, DTRXC, LOGL_DEBUG, + "Rx '%s'\n", osmo_trxc_msg_name(cmd)); + + rsp.type = OSMO_TRXC_MT_RSP; + rsp.status = 0; /* ACK all commands by default */ + OSMO_STRLCPY_ARRAY(rsp.cmd, cmd->cmd); + OSMO_STRLCPY_ARRAY(rsp.params, cmd->params); + + if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_POWERON)) { + ctrl_cmd_poweron(trx, &rsp); + } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_POWEROFF)) { + ctrl_cmd_poweroff(trx, &rsp); + } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_RXTUNE)) { + ctrl_cmd_rxtune(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_TXTUNE)) { + ctrl_cmd_txtune(trx, chan, cmd, &rsp); + } else { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, + "Unhandled command '%s'\n", cmd->cmd); + } + + LOGP_TRXCH(trx, chan, DTRXC, LOGL_DEBUG, + "Tx '%s'\n", osmo_trxc_msg_name(&rsp)); + + osmo_trx_ep_send_ctrl_msg(ep, chan, &rsp); +} diff --git a/proxy/src/main.c b/proxy/src/main.c index 5ac023a..074c019 100644 --- a/proxy/src/main.c +++ b/proxy/src/main.c @@ -42,6 +42,7 @@ #include <osmocom/proxy/proxy.h> #include <osmocom/proxy/vty.h> #include <osmocom/proxy/trx.h> +#include <osmocom/proxy/clck_gen.h> #include <osmocom/proxy/logging.h>
void *g_talloc_ctx; @@ -119,6 +120,11 @@
proxy_vty_init();
+ if (clck_gen_init() < 0) { + LOGP(DPROXY, LOGL_FATAL, "Failed to set up the TDMA clock generator\n"); + return 1; + } + handle_options(argc, argv);
rc = vty_read_config_file(g_config_file, NULL); diff --git a/proxy/src/trx.c b/proxy/src/trx.c index 6eab496..c535c03 100644 --- a/proxy/src/trx.c +++ b/proxy/src/trx.c @@ -26,6 +26,7 @@ #include <errno.h> #include <string.h>
+#include <osmocom/core/utils.h> #include <osmocom/core/talloc.h> #include <osmocom/core/linuxlist.h>
@@ -33,10 +34,12 @@
#include <osmocom/proxy/proxy.h> #include <osmocom/proxy/trx.h> +#include <osmocom/proxy/clck_gen.h> #include <osmocom/proxy/logging.h>
struct proxy_trx *proxy_trx_alloc(struct proxy_ctx *proxy, const char *name) { + const unsigned int num_chans = 1; struct proxy_trx *trx;
trx = talloc_zero(proxy, struct proxy_trx); @@ -44,11 +47,12 @@ return NULL;
trx->name = talloc_strdup(trx, name); - trx->ep = osmo_trx_ep_alloc(trx, 1); + trx->ep = osmo_trx_ep_alloc(trx, num_chans); if (trx->ep == NULL) { talloc_free(trx); return NULL; } + proxy_trx_set_num_chans(trx, num_chans);
osmo_trx_ep_set_priv(trx->ep, trx); osmo_trx_ep_set_name(trx->ep, "%s", name); @@ -86,11 +90,26 @@ { if (!trx) return; + proxy_trx_set_power(trx, false); osmo_trx_ep_free(trx->ep); llist_del(&trx->list); talloc_free(trx); }
+/*! Update the endpoint's power state; + * (de)registers it with the TDMA clock generator as appropriate. */ +void proxy_trx_set_power(struct proxy_trx *trx, bool on) +{ + if (trx->powered == on) + return; + + LOGP_TRX(trx, DTRXC, LOGL_INFO, + "Power %s\n", on ? "on" : "off"); + + trx->powered = on; + clck_gen_trx_list_updated(); +} + int proxy_trx_open(struct proxy_trx *trx) { int rc; @@ -104,5 +123,23 @@ return rc; }
+ if (rc == 0) { /* first successful open: num_chans is now fixed */ + trx->chans = talloc_zero_array(trx, struct proxy_trx_chan, trx->num_chans); + OSMO_ASSERT(trx->chans != NULL); + } + + return 0; +} + +/*! Change the number of channels; must be called before proxy_trx_open(). */ +int proxy_trx_set_num_chans(struct proxy_trx *trx, unsigned int num_chans) +{ + int rc; + + rc = osmo_trx_ep_set_num_chans(trx->ep, num_chans); + if (rc) + return rc; + + trx->num_chans = num_chans; return 0; } diff --git a/proxy/src/vty.c b/proxy/src/vty.c index 086fe50..ec986d0 100644 --- a/proxy/src/vty.c +++ b/proxy/src/vty.c @@ -179,9 +179,9 @@ int num_chans = atoi(argv[0]); int rc;
- rc = osmo_trx_ep_set_num_chans(trx->ep, num_chans); + rc = proxy_trx_set_num_chans(trx, num_chans); if (rc) { - vty_out(vty, "%% osmo_trx_ep_set_num_chans(%d) failed: rc=%d%s", + vty_out(vty, "%% proxy_trx_set_num_chans(%d) failed: rc=%d%s", num_chans, rc, VTY_NEWLINE); return CMD_WARNING; } @@ -231,7 +231,7 @@ if (laddr && strcmp(laddr, g_proxy_ctx->bind_addr) != 0) vty_out(vty, " bind-addr %s%s", laddr, VTY_NEWLINE); vty_out(vty, " base-port %u%s", osmo_trx_ep_get_base_port(trx->ep), VTY_NEWLINE); - vty_out(vty, " num-chans %u%s", osmo_trx_ep_get_num_chans(trx->ep), VTY_NEWLINE); + vty_out(vty, " num-chans %u%s", trx->num_chans, VTY_NEWLINE);
if (!osmo_trx_ep_get_clock_socket(trx->ep)) vty_out(vty, " no clock-socket%s", VTY_NEWLINE);