fixeria submitted this change.

View Change



3 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted one.

Approvals: pespin: Looks good to me, approved laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified
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(-)

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);

To view, visit change 43604. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: merged
Gerrit-Project: osmo-trx
Gerrit-Branch: master
Gerrit-Change-Id: I8c0757f15961bf0574f1305a6a8836853b66afcf
Gerrit-Change-Number: 43604
Gerrit-PatchSet: 6
Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy@sysmocom.de>
Gerrit-Reviewer: laforge <laforge@osmocom.org>
Gerrit-Reviewer: pespin <pespin@sysmocom.de>