diff options
-rw-r--r-- | include/osmocom/bsc/Makefile.am | 1 | ||||
-rw-r--r-- | include/osmocom/bsc/debug.h | 1 | ||||
-rw-r--r-- | include/osmocom/bsc/gsm_data.h | 26 | ||||
-rw-r--r-- | include/osmocom/bsc/power_control.h | 7 | ||||
-rw-r--r-- | src/osmo-bsc/Makefile.am | 1 | ||||
-rw-r--r-- | src/osmo-bsc/abis_rsl.c | 3 | ||||
-rw-r--r-- | src/osmo-bsc/bsc_vty.c | 6 | ||||
-rw-r--r-- | src/osmo-bsc/bts_vty.c | 22 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_main.c | 6 | ||||
-rw-r--r-- | src/osmo-bsc/power_control.c | 261 | ||||
-rw-r--r-- | tests/power_ctrl.vty | 4 |
11 files changed, 333 insertions, 5 deletions
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am index 3accc740a..3bccf44fd 100644 --- a/include/osmocom/bsc/Makefile.am +++ b/include/osmocom/bsc/Makefile.am @@ -62,4 +62,5 @@ noinst_HEADERS = \ penalty_timers.h \ osmo_bsc_lcls.h \ smscb.h \ + power_control.h \ $(NULL) diff --git a/include/osmocom/bsc/debug.h b/include/osmocom/bsc/debug.h index 4ad61b42e..a3cad68b7 100644 --- a/include/osmocom/bsc/debug.h +++ b/include/osmocom/bsc/debug.h @@ -29,6 +29,7 @@ enum { DCBS, DLCS, DRESET, + DLOOP, Debug_LastEntry, }; diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h index 21828d4f4..36d364c97 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -673,6 +673,27 @@ 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 lchan_power_ctrl_state { + /* Measurement pre-processing state (for dynamic mode) */ + struct gsm_power_ctrl_meas_proc_state rxlev_meas_proc; + struct gsm_power_ctrl_meas_proc_state rxqual_meas_proc; + /* Number of SACCH blocks to skip (for dynamic mode) */ + int skip_block_num; +}; + struct gsm_lchan { /* The TS that we're part of */ struct gsm_bts_trx_ts *ts; @@ -820,6 +841,8 @@ 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; + /* MS power control state */ + struct lchan_power_ctrl_state ms_power_ctrl; }; /* One Timeslot in a TRX */ @@ -1342,6 +1365,9 @@ enum gsm_power_ctrl_mode { GSM_PWR_CTRL_MODE_STATIC, /* Send MS/BS Power [Parameters] IEs (dynamic mode) */ GSM_PWR_CTRL_MODE_DYN_BTS, + /* Do not send MS/BS Power IEs and use BSC Power Loop */ + GSM_PWR_CTRL_MODE_DYN_BSC, + }; /* MS/BS Power Control Parameters */ diff --git a/include/osmocom/bsc/power_control.h b/include/osmocom/bsc/power_control.h new file mode 100644 index 000000000..82cbcb096 --- /dev/null +++ b/include/osmocom/bsc/power_control.h @@ -0,0 +1,7 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/meas_rep.h> + +int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, const 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 98822521f..a7b267d0d 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) @@ -1341,6 +1342,8 @@ static int rsl_rx_meas_res(struct msgb *msg) print_meas_rep(msg->lchan, mr); + lchan_ms_pwr_ctrl(msg->lchan, mr); + send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr); return 0; diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c index 879ae3fed..c1a6e4404 100644 --- a/src/osmo-bsc/bsc_vty.c +++ b/src/osmo-bsc/bsc_vty.c @@ -1363,6 +1363,12 @@ DEFUN(bts_resend_power_ctrl_params, return CMD_WARNING; } + if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_DYN_BTS) { + vty_out(vty, "%% Not Sending default MS/BS Power control parameters " + "because BTS%d is not using dyn-bts mode%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + if (bts->model->power_ctrl_send_def_params == NULL) { vty_out(vty, "%% Sending default MS/BS Power control parameters " "for BTS%d is not implemented%s", bts_nr, VTY_NEWLINE); diff --git a/src/osmo-bsc/bts_vty.c b/src/osmo-bsc/bts_vty.c index fb1152048..460e14a10 100644 --- a/src/osmo-bsc/bts_vty.c +++ b/src/osmo-bsc/bts_vty.c @@ -2915,10 +2915,11 @@ DEFUN(cfg_bts_power_ctrl, DEFUN_USRATTR(cfg_power_ctrl_mode, cfg_power_ctrl_mode_cmd, X(BSC_VTY_ATTR_NEW_LCHAN), - "mode (static|dyn-bts) [reset]", + "mode (static|dyn-bts|dyn-bsc) [reset]", "Power control mode\n" "Instruct the MS/BTS to use a static power level\n" "Power control to be performed dynamically by the BTS itself\n" + "Power control to be performed dynamically at this BSC\n" "Reset to default parameters for the given mode\n") { struct gsm_power_ctrl_params *params = vty->index; @@ -2935,6 +2936,13 @@ DEFUN_USRATTR(cfg_power_ctrl_mode, params->mode = GSM_PWR_CTRL_MODE_STATIC; else if (strcmp(argv[0], "dyn-bts") == 0) params->mode = GSM_PWR_CTRL_MODE_DYN_BTS; + else if (strcmp(argv[0], "dyn-bsc") == 0) { + if (params->dir == GSM_PWR_CTRL_DIR_DL) { + vty_out(vty, "%% mode dyn-bsc not supported for Downlink.%s", VTY_NEWLINE); + return CMD_WARNING; + } + params->mode = GSM_PWR_CTRL_MODE_DYN_BSC; + } return CMD_SUCCESS; } @@ -3139,6 +3147,11 @@ DEFUN_USRATTR(cfg_power_ctrl_ci_thresh, int upper = atoi(argv[2]); struct gsm_power_ctrl_meas_params *meas_params; + if (params->mode == GSM_PWR_CTRL_MODE_DYN_BSC) { + vty_out(vty, "%% C/I based power loop not possible in dyn-bsc mode!%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (params->dir != GSM_PWR_CTRL_DIR_UL) { vty_out(vty, "%% C/I based power loop only possible in Uplink!%s", VTY_NEWLINE); return CMD_WARNING; @@ -3962,8 +3975,10 @@ static void config_write_power_ctrl(struct vty *vty, unsigned int indent, cfg_out(" bs-power static %u%s", cp->bs_power_val_db, VTY_NEWLINE); break; case GSM_PWR_CTRL_MODE_DYN_BTS: + case GSM_PWR_CTRL_MODE_DYN_BSC: cfg_out("%s%s", node_name, VTY_NEWLINE); - cfg_out(" mode dyn-bts%s", VTY_NEWLINE); + cfg_out(" mode %s%s", + cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS ? "dyn-bts" : "dyn-bsc", VTY_NEWLINE); if (cp->dir == GSM_PWR_CTRL_DIR_DL) cfg_out(" bs-power dyn-max %u%s", cp->bs_power_max_db, VTY_NEWLINE); @@ -3975,7 +3990,8 @@ static void config_write_power_ctrl(struct vty *vty, unsigned int indent, /* Measurement processing / averaging parameters */ config_write_power_ctrl_meas(vty, indent + 1, &cp->rxlev_meas, "rxlev", ""); config_write_power_ctrl_meas(vty, indent + 1, &cp->rxqual_meas, "rxqual", ""); - if (cp->dir == GSM_PWR_CTRL_DIR_UL && is_osmobts(bts)) { + if (cp->dir == GSM_PWR_CTRL_DIR_UL && is_osmobts(bts) + && cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS) { config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_fr_meas, "ci", " fr-efr"); config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_hr_meas, "ci", " hr"); config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_amr_fr_meas, "ci", " amr-fr"); diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index 395a60e21..0ed00335b 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -855,6 +855,12 @@ static const struct log_info_cat osmo_bsc_categories[] = { .description = "RESET/ACK on A and Lb interfaces", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DLOOP] = { + .name = "DLOOP", + .description = "Control loops", + .color = "\033[0;34m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; static int filter_fn(const struct log_context *ctx, struct log_target *tar) diff --git a/src/osmo-bsc/power_control.c b/src/osmo-bsc/power_control.c new file mode 100644 index 000000000..6fe445544 --- /dev/null +++ b/src/osmo-bsc/power_control.c @@ -0,0 +1,261 @@ +/* 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; + + /* How many dBs measured power should be increased (+) or decreased (-) + * to reach expected power. */ + delta = CALC_TARGET(params->rxlev_meas) - rxlev; + + /* 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; + + 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 lchan_power_ctrl_state *state) +{ + /* Power control interval: how many blocks do we skip? */ + if (state->skip_block_num-- > 0) + return true; + + /* Can we be sure if ONE Report is always going to correspond + * to ONE SACCH block at the BTS? - If not this is as approximation + * but it should not hurt. */ + + /* Reset the number of SACCH blocks to be skipped: + * ctrl_interval=0 => 0 blocks to skip, + * ctrl_interval=1 => 1 blocks to skip, + * ctrl_interval=2 => 3 blocks to skip, + * so basically ctrl_interval * 2 - 1. */ + state->skip_block_num = params->ctrl_interval * 2 - 1; + return false; +} + +int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, const struct gsm_meas_rep *mr) +{ + struct lchan_power_ctrl_state *state = &lchan->ms_power_ctrl; + 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; + uint8_t ms_power_lvl = ms_pwr_ctl_lvl(band, mr->ms_l1.pwr); + int8_t ul_rssi_dbm; + bool ignore; + + if (params == NULL) + return 0; + /* Not doing the power loop here if we are not handling it */ + if (params->mode != GSM_PWR_CTRL_MODE_DYN_BSC) + return 0; + + /* Shall we skip current block based on configured interval? */ + if (ctrl_interval_skip_block(params, state)) + return 0; + + /* If DTx is active on Uplink, + * use the '-SUB', otherwise '-FULL': */ + if (mr->flags & MEAS_REP_F_UL_DTX) + ul_rssi_dbm = rxlev2dbm(mr->ul.sub.rx_lev); + else + ul_rssi_dbm = rxlev2dbm(mr->ul.full.rx_lev); + + ms_dbm = ms_pwr_dbm(band, ms_power_lvl); + if (ms_dbm < 0) { + LOGPLCHAN(lchan, DLOOP, 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; + rxlev_avg = do_avg_algo(¶ms->rxlev_meas, &state->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 ms max power for this BTS */ + 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, DLOOP, 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, DLOOP, 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\n", + new_power_lvl, ms_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)); + return 0; + } + + LOGPLCHAN(lchan, DLOOP, 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\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)); + + lchan_update_ms_power_ctrl_level(lchan, new_dbm); + + return 1; + +} diff --git a/tests/power_ctrl.vty b/tests/power_ctrl.vty index 491adcadd..9747ec22a 100644 --- a/tests/power_ctrl.vty +++ b/tests/power_ctrl.vty @@ -28,7 +28,7 @@ OsmoBSC(config-net-bts)# list with-flags OsmoBSC(config-net-bts)# bs-power-control OsmoBSC(config-bs-power-ctrl)# list with-flags ... - . l. mode (static|dyn-bts) [reset] + . l. mode (static|dyn-bts|dyn-bsc) [reset] . l. bs-power (static|dyn-max) <0-30> . lv ctrl-interval <0-31> . lv step-size inc <2-6> red <2-4> @@ -109,7 +109,7 @@ OsmoBSC(config-bs-power-ctrl)# exit OsmoBSC(config-net-bts)# ms-power-control OsmoBSC(config-ms-power-ctrl)# list with-flags ... - . l. mode (static|dyn-bts) [reset] + . l. mode (static|dyn-bts|dyn-bsc) [reset] . l. bs-power (static|dyn-max) <0-30> . lv ctrl-interval <0-31> . lv step-size inc <2-6> red <2-4> |