aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/bsc/gsm_data.h14
-rw-r--r--include/osmocom/bsc/power_control.h9
-rw-r--r--src/osmo-bsc/Makefile.am1
-rw-r--r--src/osmo-bsc/abis_rsl.c3
-rw-r--r--src/osmo-bsc/power_control.c323
5 files changed, 350 insertions, 0 deletions
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index b16614695..9369b5d9a 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -665,6 +665,19 @@ struct lchan_modify_info {
#define INTERF_DBM_UNKNOWN 0
#define INTERF_BAND_UNKNOWN 0xff
+/* Measurement pre-processing state */
+struct gsm_power_ctrl_meas_proc_state {
+ /* Number of measurements processed */
+ unsigned int meas_num;
+ /* Algorithm specific data */
+ union {
+ struct {
+ /* Scaled up 100 times average value */
+ int Avg100;
+ } ewma;
+ };
+};
+
struct gsm_lchan {
/* The TS that we're part of */
struct gsm_bts_trx_ts *ts;
@@ -808,6 +821,7 @@ struct gsm_lchan {
/* Actual reported interference band index, or INTERF_BAND_UNKNOWN if this lchan was not included in the most
* recent Resource Indication. */
uint8_t interf_band;
+ struct gsm_power_ctrl_meas_proc_state rxlev_meas_proc;
};
/* One Timeslot in a TRX */
diff --git a/include/osmocom/bsc/power_control.h b/include/osmocom/bsc/power_control.h
new file mode 100644
index 000000000..1e4ba0242
--- /dev/null
+++ b/include/osmocom/bsc/power_control.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/meas_rep.h>
+
+void crude_ms_power_loop(struct gsm_lchan *lchan, struct gsm_meas_rep *mr);
+
+int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, struct gsm_meas_rep *mr);
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index 899169713..19af5694f 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -106,6 +106,7 @@ libbsc_la_SOURCES = \
smscb.c \
cbch_scheduler.c \
cbsp_link.c \
+ power_control.c \
$(NULL)
libbsc_la_LIBADD = \
diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c
index c7399ebef..af19b806a 100644
--- a/src/osmo-bsc/abis_rsl.c
+++ b/src/osmo-bsc/abis_rsl.c
@@ -55,6 +55,7 @@
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/power_control.h>
static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan,
struct gsm_meas_rep *resp)
@@ -1338,6 +1339,8 @@ static int rsl_rx_meas_res(struct msgb *msg)
LOGP(DRSL, LOGL_DEBUG, "%s: meas_rep_count++=%d meas_rep_last_seen_nr=%u\n",
gsm_lchan_name(mr->lchan), mr->lchan->meas_rep_count, mr->lchan->meas_rep_last_seen_nr);
+ lchan_ms_pwr_ctrl(msg->lchan, mr);
+
print_meas_rep(msg->lchan, mr);
send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr);
diff --git a/src/osmo-bsc/power_control.c b/src/osmo-bsc/power_control.c
new file mode 100644
index 000000000..e848f6724
--- /dev/null
+++ b/src/osmo-bsc/power_control.c
@@ -0,0 +1,323 @@
+/* MS Power Control Loop L1 */
+
+/* (C) 2014 by Holger Hans Peter Freyther
+ * (C) 2020-2021 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
+ *
+ * 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 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 <unistd.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/power_control.h>
+
+/* We don't want to deal with floating point, so we scale up */
+#define EWMA_SCALE_FACTOR 100
+/* EWMA_SCALE_FACTOR/2 = +50: Round to nearest value when downscaling, otherwise floor() is applied. */
+#define EWMA_ROUND_FACTOR (EWMA_SCALE_FACTOR / 2)
+
+/* Base Low-Pass Single-Pole IIR Filter (EWMA) formula:
+ *
+ * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1]
+ *
+ * where parameter 'a' determines how much weight of the latest measurement value
+ * 'Val[n]' carries vs the weight of the accumulated average 'Avg[n - 1]'. The
+ * value of 'a' is usually a float in range 0 .. 1, so:
+ *
+ * - value 0.5 gives equal weight to both 'Val[n]' and 'Avg[n - 1]';
+ * - value 1.0 means no filtering at all (pass through);
+ * - value 0.0 makes no sense.
+ *
+ * Further optimization:
+ *
+ * Avg[n] = a * Val[n] + Avg[n - 1] - a * Avg[n - 1]
+ * ^^^^^^ ^^^^^^^^^^
+ *
+ * a) this can be implemented in C using '+=' operator:
+ *
+ * Avg += a * Val - a * Avg
+ * Avg += a * (Val - Avg)
+ *
+ * b) everything is scaled up by 100 to avoid floating point stuff:
+ *
+ * Avg100 += A * (Val - Avg)
+ *
+ * where 'Avg100' is 'Avg * 100' and 'A' is 'a * 100'.
+ *
+ * For more details, see:
+ *
+ * https://en.wikipedia.org/wiki/Moving_average
+ * https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter
+ * https://tomroelandts.com/articles/low-pass-single-pole-iir-filter
+ */
+static int do_pf_ewma(const struct gsm_power_ctrl_meas_params *mp,
+ struct gsm_power_ctrl_meas_proc_state *mps,
+ const int Val)
+{
+ const uint8_t A = mp->ewma.alpha;
+ int *Avg100 = &mps->ewma.Avg100;
+
+ /* We don't have 'Avg[n - 1]' if this is the first run */
+ if (mps->meas_num++ == 0) {
+ *Avg100 = Val * EWMA_SCALE_FACTOR;
+ return Val;
+ }
+
+ *Avg100 += A * (Val - (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR);
+ return (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR;
+}
+
+/* Calculate target RxLev value from lower/upper thresholds */
+#define CALC_TARGET(mp) \
+ ((mp).lower_thresh + (mp).upper_thresh) / 2
+
+static int do_avg_algo(const struct gsm_power_ctrl_meas_params *mp,
+ struct gsm_power_ctrl_meas_proc_state *mps,
+ const int val)
+{
+ int val_avg;
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
+ val_avg = do_pf_ewma(mp, mps, val);
+ break;
+ /* TODO: implement other pre-processing methods */
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
+ default:
+ /* No filtering (pass through) */
+ val_avg = val;
+ }
+ return val_avg;
+}
+/* Calculate a 'delta' value (for the given MS/BS power control parameters)
+ * to be applied to the current Tx power level to approach the target level. */
+static int calc_delta_rxlev(const struct gsm_power_ctrl_params *params, const uint8_t rxlev)
+{
+ int delta;
+
+ /* Check if RxLev is within the threshold window */
+ if (rxlev >= params->rxlev_meas.lower_thresh &&
+ rxlev <= params->rxlev_meas.upper_thresh)
+ return 0;
+
+
+ printf("%d, is not in window\n", rxlev);
+ /* How many dBs measured power should be increased (+) or decreased (-)
+ * to reach expected power. */
+ delta = CALC_TARGET(params->rxlev_meas) - rxlev;
+
+ printf("New delta: %d\n", delta);
+ /* Don't ever change more than PWR_{LOWER,RAISE}_MAX_DBM during one loop
+ * iteration, i.e. reduce the speed at which the MS transmit power can
+ * change. A higher value means a lower level (and vice versa) */
+ if (delta > params->inc_step_size_db)
+ delta = params->inc_step_size_db;
+ else if (delta < -params->red_step_size_db)
+ delta = -params->red_step_size_db;
+
+ printf("New delta: %d\n", delta);
+ return delta;
+}
+
+/* Shall we skip current block based on configured interval? */
+static bool ctrl_interval_skip_block(const struct gsm_power_ctrl_params *params,
+ struct gsm_meas_rep *mr)
+{
+ /* Power control interval: how many blocks do we skip? */
+ //if (mr->nr % params->ctrl_interval)
+ // return true;
+ return false;
+}
+
+static const struct gsm_power_ctrl_meas_params *lchan_get_ci_thresholds(const struct gsm_lchan *lchan)
+{
+ const struct gsm_power_ctrl_params *params = &lchan->ts->trx->bts->ms_power_ctrl;
+
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ return &params->ci_sdcch_meas;
+ case GSM_LCHAN_PDTCH:
+ return &params->ci_gprs_meas;
+ case GSM_LCHAN_TCH_F:
+ if (lchan->current_ch_mode_rate.chan_mode == GSM48_CMODE_SPEECH_AMR)
+ return &params->ci_amr_fr_meas;
+ else
+ return &params->ci_fr_meas;
+ case GSM_LCHAN_TCH_H:
+ if (lchan->current_ch_mode_rate.chan_mode == GSM48_CMODE_SPEECH_AMR)
+ return &params->ci_amr_hr_meas;
+ else
+ return &params->ci_hr_meas;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+void crude_ms_power_loop(struct gsm_lchan *lchan, struct gsm_meas_rep *mr)
+{
+ uint8_t cur_lev;
+ int8_t cur_pwr_rep;
+ int8_t cur_pwr_lchan;
+ enum gsm_band band = lchan->ts->trx->bts->band;
+ int8_t new_pwr;
+
+ /* -110 + cur_lev = dBm */
+ cur_lev = mr->ul.full.rx_lev;
+ cur_pwr_rep = mr->ms_l1.pwr; /* MS power in dBm */
+ cur_pwr_lchan = lchan->ms_power;
+ printf("Uplink receive level is [%d](%ddBm) with power [%d](%ddBm) LCHAN:(%d)(%ddBm)\n",
+ cur_lev, (-110 + cur_lev),
+ ms_pwr_ctl_lvl(band, cur_pwr_rep), cur_pwr_rep,
+ cur_pwr_lchan, ms_pwr_dbm(band, cur_pwr_lchan));
+
+ if (cur_lev > 45) {
+ if (cur_pwr_lchan >= 19)
+ return;
+ new_pwr = cur_pwr_lchan + 1;
+ lchan->ms_power = new_pwr;
+ printf ("We should change to %d.\n", lchan->ms_power);
+ rsl_chan_ms_power_ctrl(lchan);
+ }
+ if (cur_lev < 45) {
+ if (cur_pwr_lchan == 0)
+ return;
+ new_pwr = cur_pwr_lchan - 1;
+ lchan->ms_power = new_pwr;
+ printf ("We should change to %d.\n", lchan->ms_power);
+ rsl_chan_ms_power_ctrl(lchan);
+ }
+}
+
+
+int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, struct gsm_meas_rep *mr)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct gsm_bts *bts = trx->bts;
+ enum gsm_band band = bts->band;
+ const struct gsm_power_ctrl_params *params = &bts->ms_power_ctrl;
+ int8_t new_power_lvl; /* TS 05.05 power level */
+ int8_t ms_dbm, new_dbm, current_dbm, bsc_max_dbm;
+ uint8_t rxlev_avg;
+ const struct gsm_power_ctrl_meas_params *ci_meas;
+ uint8_t ms_power_lvl = ms_pwr_ctl_lvl(band, mr->ms_l1.pwr);
+ int8_t ul_rssi_dbm = rxlev2dbm(mr->ul.full.rx_lev);
+ bool ignore;
+
+ if (params == NULL)
+ return 0;
+ /* Not doing the power loop here if the BTS is handling it */
+ //if (params->mode != GSM_PWR_CTRL_MODE_STATIC)
+ // return 0;
+
+ /* Shall we skip current block based on configured interval?
+ * FIXME: Do this right or abandon it? */
+ if (ctrl_interval_skip_block(params, mr))
+ return 0;
+
+ ms_dbm = ms_pwr_dbm(band, ms_power_lvl);
+ if (ms_dbm < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE,
+ "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n",
+ ms_power_lvl, gsm_band_name(band));
+ return 0;
+ }
+ bsc_max_dbm = bts->ms_max_power;
+ /* FIXME: See below, the one set on Chan Act?
+ * Doesn't it change now, if we change it? */
+ if (bsc_max_dbm < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE,
+ "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n",
+ lchan->ms_power, gsm_band_name(band));
+ return 0;
+ }
+ ci_meas = lchan_get_ci_thresholds(lchan);
+ rxlev_avg = do_avg_algo(&params->rxlev_meas, &lchan->rxlev_meas_proc, dbm2rxlev(ul_rssi_dbm));
+ new_dbm = ms_dbm + calc_delta_rxlev(params, rxlev_avg);
+
+ /* Make sure new_dbm is never negative. ms_pwr_ctl_lvl() can later on
+ cope with any unsigned dbm value, regardless of band minimal value. */
+ if (new_dbm < 0)
+ new_dbm = 0;
+ /* Don't ask for smaller ms power level than the one set by BSC upon RSL CHAN ACT */
+ if (new_dbm > bsc_max_dbm)
+ new_dbm = bsc_max_dbm;
+
+ new_power_lvl = ms_pwr_ctl_lvl(band, new_dbm);
+ if (new_power_lvl < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE,
+ "Failed to retrieve power level for %" PRId8 " dBm on band %d\n",
+ new_dbm, band);
+ return 0;
+ }
+
+ current_dbm = ms_pwr_dbm(band, lchan->ms_power);
+
+ /* In this Power Control Loop, we infer a new good MS Power Level based
+ * on the previous MS Power Level announced by the MS (not the previous
+ * one we requested!) together with the related computed measurements.
+ * Hence, and since we allow for several good MS Power Levels falling into our
+ * thresholds, we could finally converge into an oscillation loop where
+ * the MS bounces between 2 different correct MS Power levels all the
+ * time, due to the fact that we "accept" and "request back" whatever
+ * good MS Power Level we received from the MS, but at that time the MS
+ * will be transmitting using the previous MS Power Level we
+ * requested, which we will later "accept" and "request back" on next loop
+ * iteration. As a result MS effectively bounces between those 2 MS
+ * Power Levels.
+ * In order to fix this permanent oscillation, if current MS_PWR used/announced
+ * by MS is good ("ms_dbm == new_dbm", hence within thresholds and no change
+ * required) but has higher Tx power than the one we last requested, we ignore
+ * it and keep requesting for one with lower Tx power. This way we converge to
+ * the lowest good Tx power avoiding oscillating over values within thresholds.
+ */
+ ignore = (ms_dbm == new_dbm && ms_dbm > current_dbm);
+
+ if (lchan->ms_power == new_power_lvl || ignore) {
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Keeping MS power at control level %d (%d dBm): "
+ "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm,"
+ " C/I[thresh %d..%d] dB\n",
+ new_power_lvl, new_dbm, ms_power_lvl, bsc_max_dbm, ul_rssi_dbm, rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh),
+ ci_meas->lower_thresh, ci_meas->upper_thresh);
+ return 0;
+ }
+
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "%s MS power control level %d (%d dBm) => %d (%d dBm): "
+ "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm,"
+ " C/I[thresh %d..%d] dB\n",
+ (new_dbm > current_dbm) ? "Raising" : "Lowering",
+ lchan->ms_power, current_dbm, new_power_lvl, new_dbm, ms_power_lvl,
+ bsc_max_dbm, ul_rssi_dbm, rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh),
+ ci_meas->lower_thresh, ci_meas->upper_thresh);
+
+ /* store the resulting new MS power level in the lchan */
+ lchan->ms_power = new_power_lvl;
+ rsl_chan_ms_power_ctrl(lchan);
+
+ return 1;
+
+}
+