fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmo-trx/+/43606?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: implement RF path simulation ......................................................................
proxy: implement RF path simulation
Compute real ToA/RSSI/CI values for forwarded bursts instead of the fixed placeholder constants in burst_fwd.c. Each channel gets its own struct path_sim_state, tracking Tx power/attenuation, Timing Advance, reported ToA/RSSI/CI, and burst-dropping parameters. RSSI defaults to a path-loss formula (Tx power - burst attenuation - fixed path loss) unless overridden via FAKE_RSSI.
Add the CTRL commands needed to drive this: SETTA, SETPOWER, NOMTXPOWER, RFMUTE, and the FAKE_TOA/FAKE_RSSI/FAKE_CI/FAKE_DROP simulation helpers. RFMUTE and burst dropping (FAKE_DROP) turn a forwarded burst into a NOPE.ind, matching the existing NOPE.req handling path.
Change-Id: Iefa51d6b0e0bba1f12bdbcdf576528e7d3588b8f Related: OS#6672 --- M proxy/Makefile.am A proxy/include/osmocom/proxy/path_sim.h M proxy/include/osmocom/proxy/trx.h M proxy/src/Makefile.am M proxy/src/burst_fwd.c M proxy/src/ctrl_cmd.c A proxy/src/path_sim.c M proxy/src/trx.c 8 files changed, 367 insertions(+), 13 deletions(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/proxy/Makefile.am b/proxy/Makefile.am index e99dd63..909c698 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -5,6 +5,7 @@ noinst_HEADERS = \ include/osmocom/proxy/clck_gen.h \ include/osmocom/proxy/logging.h \ + include/osmocom/proxy/path_sim.h \ include/osmocom/proxy/proxy.h \ include/osmocom/proxy/trx.h \ include/osmocom/proxy/vty.h \ diff --git a/proxy/include/osmocom/proxy/path_sim.h b/proxy/include/osmocom/proxy/path_sim.h new file mode 100644 index 0000000..d27a3b1 --- /dev/null +++ b/proxy/include/osmocom/proxy/path_sim.h @@ -0,0 +1,32 @@ +#pragma once + +#include <stdint.h> + +struct proxy_trx_chan; +struct osmo_trxd_burst_ind; +struct osmo_trxd_burst_req; + +#define PATH_SIM_F_FAKE_RSSI (1 << 0) /*!< use rssi instead of the path-loss formula */ + +/*! Per-channel RF path simulation state: ToA/RSSI/C-I measurement emulation + * and burst dropping (simulated path loss / weak signal / interference). */ +struct path_sim_state { + uint32_t flags; /*!< PATH_SIM_F_* */ + int tx_power; /*!< dBm, nominal Tx power (NOMTXPOWER) */ + int tx_att; /*!< dB, Tx power attenuation (SETPOWER) */ + int ta; /*!< Timing Advance, symbol periods (SETTA) */ + int toa256; /*!< reported ToA, 1/256 symbol periods (FAKE_TOA) */ + int toa256_jitter; /*!< +/- random jitter around toa256 (FAKE_TOA "<val> <threshold>") */ + int rssi; /*!< reported RSSI in dBm, if PATH_SIM_F_FAKE_RSSI (FAKE_RSSI) */ + int rssi_jitter; /*!< +/- random jitter around rssi (FAKE_RSSI "<val> <threshold>") */ + int ci; /*!< reported C/I in cB (FAKE_CI) */ + int ci_jitter; /*!< +/- random jitter around ci (FAKE_CI "<val> <threshold>") */ + unsigned int burst_drop_amount; /*!< bursts left to drop (FAKE_DROP) */ + unsigned int burst_drop_period; /*!< drop if (fn % period) == 0 */ +}; + +void path_sim_state_reset(struct path_sim_state *ps); +void path_sim_apply(struct osmo_trxd_burst_ind *bi, + struct proxy_trx_chan *dst, + const struct osmo_trxd_burst_req *br, + const struct proxy_trx_chan *src); diff --git a/proxy/include/osmocom/proxy/trx.h b/proxy/include/osmocom/proxy/trx.h index 52d2e6a..f91acbf 100644 --- a/proxy/include/osmocom/proxy/trx.h +++ b/proxy/include/osmocom/proxy/trx.h @@ -6,6 +6,8 @@ #include <osmocom/core/linuxlist.h> #include <osmocom/core/logging.h>
+#include <osmocom/proxy/path_sim.h> + struct osmo_trx_ep; struct proxy_ctx;
@@ -15,6 +17,8 @@ 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 */ + bool rf_muted; /*!< RFMUTE: force NOPE.ind on bursts this channel transmits */ + struct path_sim_state path_sim; /*!< RF path simulation state (path_sim.c) */ };
/*! One virtual transceiver endpoint */ diff --git a/proxy/src/Makefile.am b/proxy/src/Makefile.am index 5c8b931..4c3678e 100644 --- a/proxy/src/Makefile.am +++ b/proxy/src/Makefile.am @@ -21,6 +21,7 @@ clck_gen.c \ ctrl_cmd.c \ burst_fwd.c \ + path_sim.c \ logging.c \ $(NULL)
diff --git a/proxy/src/burst_fwd.c b/proxy/src/burst_fwd.c index 2a9f22d..1254e02 100644 --- a/proxy/src/burst_fwd.c +++ b/proxy/src/burst_fwd.c @@ -32,14 +32,9 @@
#include <osmocom/proxy/proxy.h> #include <osmocom/proxy/trx.h> +#include <osmocom/proxy/path_sim.h> #include <osmocom/proxy/logging.h>
-/* Placeholder measurement values, until path_sim.c computes them for real - * (matches fake_trx.py's FakeTRX nominal defaults: no timing offset, and - * -60 dBm = 50 dBm nominal Tx power - 0 dB Tx attenuation - 110 dB path loss). */ -#define BURST_FWD_TOA256_DEFAULT 0 -#define BURST_FWD_RSSI_DEFAULT -60 - #define OSMO_TRXD_F_COMMON_MASK ( \ OSMO_TRXD_F_NOPE_REQ | \ OSMO_TRXD_F_MOD_TYPE | \ @@ -47,15 +42,14 @@ OSMO_TRXD_F_TRX_NUM \ )
-static void burst_fwd_to_chan(struct proxy_trx *dst, unsigned int dst_chan, +static void burst_fwd_to_chan(struct proxy_trx *src, unsigned int src_chan, + struct proxy_trx *dst, unsigned int dst_chan, const struct osmo_trxd_burst_req *br) { struct osmo_trxd_burst_ind bi = { .flags = br->flags & OSMO_TRXD_F_COMMON_MASK, .fn = br->fn, .tn = br->tn, - .toa256 = BURST_FWD_TOA256_DEFAULT, - .rssi = BURST_FWD_RSSI_DEFAULT, .burst_len = br->burst_len, .mod = br->mod, .tsc_set = br->tsc_set, @@ -64,6 +58,8 @@ };
osmo_ubit2sbit(bi.burst, br->burst, br->burst_len); + path_sim_apply(&bi, &dst->chans[dst_chan], + br, &src->chans[src_chan]);
osmo_trx_ep_send_burst_ind(dst->ep, dst_chan, &bi); } @@ -74,9 +70,9 @@ void osmo_trx_ep_rx_burst_req(struct osmo_trx_ep *ep, unsigned int chan, const struct osmo_trxd_burst_req *br) { - const struct proxy_trx *src = osmo_trx_ep_get_priv(ep); - const uint32_t tx_freq = src->chans[chan].tx_freq; - struct proxy_trx *dst; + struct proxy_trx *src = osmo_trx_ep_get_priv(ep); + struct proxy_trx *dst = NULL; + uint32_t tx_freq;
if (!src->powered) { LOGP_TRXCH(src, chan, DTRXD, LOGL_NOTICE, @@ -84,6 +80,8 @@ return; }
+ tx_freq = src->chans[chan].tx_freq; + llist_for_each_entry(dst, &g_proxy_ctx->trx_list, list) { unsigned int dst_chan;
@@ -93,7 +91,7 @@ for (dst_chan = 0; dst_chan < dst->num_chans; dst_chan++) { if (dst->chans[dst_chan].rx_freq != tx_freq) continue; - burst_fwd_to_chan(dst, dst_chan, br); + burst_fwd_to_chan(src, chan, dst, dst_chan, br); } } } diff --git a/proxy/src/ctrl_cmd.c b/proxy/src/ctrl_cmd.c index 281cc13..449fe81 100644 --- a/proxy/src/ctrl_cmd.c +++ b/proxy/src/ctrl_cmd.c @@ -30,8 +30,17 @@ #include <osmocom/trx/trxc.h>
#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/path_sim.h> #include <osmocom/proxy/logging.h>
+/* Not part of the well-known OSMO_TRXC_CMD_* verbs (libosmo-trx/trxc.h) since + * they are specific to this transceiver's RF path simulation. */ +#define CTRL_CMD_SETTA "SETTA" +#define CTRL_CMD_FAKE_TOA "FAKE_TOA" +#define CTRL_CMD_FAKE_RSSI "FAKE_RSSI" +#define CTRL_CMD_FAKE_CI "FAKE_CI" +#define CTRL_CMD_FAKE_DROP "FAKE_DROP" + static void ctrl_cmd_poweron(struct proxy_trx *trx, struct osmo_trxc_msg *rsp) { if (trx->powered) { @@ -96,6 +105,175 @@ snprintf(rsp->params, sizeof(rsp->params), "%u", freq_khz); }
+static void ctrl_cmd_setta(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + int ta; + + if (osmo_trxc_msg_params_scan(cmd, "%d", &ta) != 1) { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): failed to parse Timing Advance: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + trx->chans[chan].path_sim.ta = ta; + + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, "Timing Advance set to %d\n", ta); +} + +static void ctrl_cmd_setpower(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + int att; + + if (osmo_trxc_msg_params_scan(cmd, "%d", &att) != 1) { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse Tx power attenuation: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + trx->chans[chan].path_sim.tx_att = att; + + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, "Tx power attenuation set to %d dB\n", att); +} + +static void ctrl_cmd_nomtxpower(struct proxy_trx *trx, unsigned int chan, struct osmo_trxc_msg *rsp) +{ + snprintf(rsp->params, sizeof(rsp->params), "%d", trx->chans[chan].path_sim.tx_power); +} + +static void ctrl_cmd_rfmute(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + int mute; + + if (osmo_trxc_msg_params_scan(cmd, "%d", &mute) != 1) { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse command arguments: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + trx->chans[chan].rf_muted = mute > 0; + + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, + "RF mute %s\n", trx->chans[chan].rf_muted ? "on" : "off"); +} + +/* FAKE_TOA/FAKE_RSSI/FAKE_CI: "<delta>" adjusts the current value by delta; + * "<value> <threshold>" sets an absolute value with a +/-threshold random + * jitter applied on every forwarded burst (see path_sim_apply()). */ + +static void ctrl_cmd_fake_toa(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + struct path_sim_state *ps = &trx->chans[chan].path_sim; + int value, threshold; + + if (osmo_trxc_msg_params_scan(cmd, "%d %d", &value, &threshold) == 2) { + if (threshold < 0) { + rsp->status = 1; + return; + } + ps->toa256 = value; + ps->toa256_jitter = threshold; + } else if (osmo_trxc_msg_params_scan(cmd, "%d", &value) == 1) { + ps->toa256 += value; + } else { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse command arguments: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + } +} + +static void ctrl_cmd_fake_rssi(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + struct path_sim_state *ps = &trx->chans[chan].path_sim; + int value, threshold; + + if (osmo_trxc_msg_params_scan(cmd, "%d %d", &value, &threshold) == 2) { + if (threshold < 0) { + rsp->status = 1; + return; + } + ps->rssi = value; + ps->rssi_jitter = threshold; + } else if (osmo_trxc_msg_params_scan(cmd, "%d", &value) == 1) { + ps->rssi = value; + ps->rssi_jitter = 0; + } else { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse command arguments: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + ps->flags |= PATH_SIM_F_FAKE_RSSI; +} + +static void ctrl_cmd_fake_ci(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + struct path_sim_state *ps = &trx->chans[chan].path_sim; + int value, threshold; + + if (osmo_trxc_msg_params_scan(cmd, "%d %d", &value, &threshold) == 2) { + if (threshold < 0) { + rsp->status = 1; + return; + } + ps->ci = value; + ps->ci_jitter = threshold; + } else if (osmo_trxc_msg_params_scan(cmd, "%d", &value) == 1) { + ps->ci += value; + } else { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse command arguments: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + } +} + +static void ctrl_cmd_fake_drop(struct proxy_trx *trx, unsigned int chan, + const struct osmo_trxc_msg *cmd, struct osmo_trxc_msg *rsp) +{ + struct path_sim_state *ps = &trx->chans[chan].path_sim; + int amount, period; + + if (osmo_trxc_msg_params_scan(cmd, "%d %d", &amount, &period) == 2) { + if (amount < 0 || period <= 0) { + rsp->status = 1; + return; + } + } else if (osmo_trxc_msg_params_scan(cmd, "%d", &amount) == 1) { + if (amount < 0) { + rsp->status = 1; + return; + } + period = 1; + } else { + LOGP_TRXCH(trx, chan, DTRXC, LOGL_ERROR, + "%s(): Failed to parse command arguments: '%s'\n", + __func__, osmo_trxc_msg_name(cmd)); + rsp->status = 1; + return; + } + + ps->burst_drop_amount = amount; + ps->burst_drop_period = period; + + LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, + "Dropping %d burst(s), every %d frame(s)\n", amount, period); +} + void osmo_trx_ep_rx_ctrl_msg(struct osmo_trx_ep *ep, unsigned int chan, const struct osmo_trxc_msg *cmd) { @@ -118,6 +296,22 @@ ctrl_cmd_rxtune(trx, chan, cmd, &rsp); } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_TXTUNE)) { ctrl_cmd_txtune(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_SETPOWER)) { + ctrl_cmd_setpower(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_NOMTXPOWER)) { + ctrl_cmd_nomtxpower(trx, chan, &rsp); + } else if (!strcmp(cmd->cmd, OSMO_TRXC_CMD_RFMUTE)) { + ctrl_cmd_rfmute(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, CTRL_CMD_SETTA)) { + ctrl_cmd_setta(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, CTRL_CMD_FAKE_TOA)) { + ctrl_cmd_fake_toa(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, CTRL_CMD_FAKE_RSSI)) { + ctrl_cmd_fake_rssi(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, CTRL_CMD_FAKE_CI)) { + ctrl_cmd_fake_ci(trx, chan, cmd, &rsp); + } else if (!strcmp(cmd->cmd, CTRL_CMD_FAKE_DROP)) { + ctrl_cmd_fake_drop(trx, chan, cmd, &rsp); } else { LOGP_TRXCH(trx, chan, DTRXC, LOGL_INFO, "Unhandled command '%s'\n", cmd->cmd); diff --git a/proxy/src/path_sim.c b/proxy/src/path_sim.c new file mode 100644 index 0000000..b835f76 --- /dev/null +++ b/proxy/src/path_sim.c @@ -0,0 +1,121 @@ +/*! \file src/path_sim.c + * osmo-trx-proxy: RF path simulation (ToA/RSSI/C-I, burst dropping, RF mute). */ + +/* + * (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 <stdlib.h> + +#include <osmocom/trx/trxd.h> + +#include <osmocom/proxy/trx.h> +#include <osmocom/proxy/path_sim.h> + +#define PATH_SIM_NOMINAL_TX_POWER_DEFAULT 50 /* dBm */ +#define PATH_SIM_TX_ATT_DEFAULT 0 /* dB */ +#define PATH_SIM_PATH_LOSS_DEFAULT 110 /* dB */ +#define PATH_SIM_CI_DEFAULT 90 /* cB */ + +/* Values reported for NOPE.ind (burst dropped / RF muted) */ +#define PATH_SIM_TOA256_NOISE_DEFAULT 0 +#define PATH_SIM_RSSI_NOISE_DEFAULT -110 +#define PATH_SIM_CI_NOISE_DEFAULT -30 + +/*! Reset a channel's RF path simulation state to nominal defaults. */ +void path_sim_state_reset(struct path_sim_state *ps) +{ + ps->flags = 0; + ps->tx_power = PATH_SIM_NOMINAL_TX_POWER_DEFAULT; + ps->tx_att = PATH_SIM_TX_ATT_DEFAULT; + ps->ta = 0; + ps->toa256 = 0; + ps->toa256_jitter = 0; + ps->rssi = PATH_SIM_NOMINAL_TX_POWER_DEFAULT - + PATH_SIM_TX_ATT_DEFAULT - PATH_SIM_PATH_LOSS_DEFAULT; + ps->rssi_jitter = 0; + ps->ci = PATH_SIM_CI_DEFAULT; + ps->ci_jitter = 0; + ps->burst_drop_amount = 0; + ps->burst_drop_period = 1; +} + +/* Path loss simulation: burst dropping. Returns true if the burst at the + * given frame number is to be dropped (and consumes one drop credit). */ +static bool path_sim_burst_drop(struct path_sim_state *dst, uint32_t fn) +{ + if (dst->burst_drop_amount == 0) + return false; + + if (fn % dst->burst_drop_period == 0) { + dst->burst_drop_amount--; + return true; + } + + return false; +} + +/* Uniform random offset in [-threshold, +threshold], or 0 if threshold <= 0. */ +static int path_sim_jitter(int threshold) +{ + if (threshold <= 0) + return 0; + + return (rand() % (2 * threshold + 1)) - threshold; +} + +/*! Fill in the RF path simulation results (ToA, RSSI, C/I, burst dropping, + * RF mute) of a BURST.ind that is about to be forwarded from src to dst. */ +void path_sim_apply(struct osmo_trxd_burst_ind *bi, + struct proxy_trx_chan *dst, + const struct osmo_trxd_burst_req *br, + const struct proxy_trx_chan *src) +{ + bool nope = bi->flags & OSMO_TRXD_F_NOPE_IND; + + if (src->rf_muted) + nope = true; + else if (!nope) + nope = path_sim_burst_drop(&dst->path_sim, br->fn); + + if (nope) { + bi->flags |= OSMO_TRXD_F_NOPE_IND | OSMO_TRXD_F_CI_CB; + bi->burst_len = 0; + bi->toa256 = PATH_SIM_TOA256_NOISE_DEFAULT; + bi->rssi = PATH_SIM_RSSI_NOISE_DEFAULT; + bi->ci_cb = PATH_SIM_CI_NOISE_DEFAULT; + return; + } + + bi->toa256 = dst->path_sim.toa256 + path_sim_jitter(dst->path_sim.toa256_jitter); + if (src->path_sim.ta != 0) + bi->toa256 -= src->path_sim.ta * 256; + + if (dst->path_sim.flags & PATH_SIM_F_FAKE_RSSI) { + bi->rssi = dst->path_sim.rssi + path_sim_jitter(dst->path_sim.rssi_jitter); + } else { + int tx_power = src->path_sim.tx_power - src->path_sim.tx_att; + bi->rssi = tx_power - (int)br->att - PATH_SIM_PATH_LOSS_DEFAULT; + } + + bi->ci_cb = dst->path_sim.ci + path_sim_jitter(dst->path_sim.ci_jitter); + bi->flags |= OSMO_TRXD_F_CI_CB; +} diff --git a/proxy/src/trx.c b/proxy/src/trx.c index c535c03..7087d6a 100644 --- a/proxy/src/trx.c +++ b/proxy/src/trx.c @@ -35,6 +35,7 @@ #include <osmocom/proxy/proxy.h> #include <osmocom/proxy/trx.h> #include <osmocom/proxy/clck_gen.h> +#include <osmocom/proxy/path_sim.h> #include <osmocom/proxy/logging.h>
struct proxy_trx *proxy_trx_alloc(struct proxy_ctx *proxy, const char *name) @@ -126,6 +127,8 @@ 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); + for (unsigned int i = 0; i < trx->num_chans; i++) + path_sim_state_reset(&trx->chans[i].path_sim); }
return 0;