diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/Makefile.am | 4 | ||||
-rw-r--r-- | src/common/bts.c | 6 | ||||
-rw-r--r-- | src/common/tx_power.c | 273 | ||||
-rw-r--r-- | src/common/vty.c | 82 |
4 files changed, 364 insertions, 1 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am index ea2a742b..77f73b47 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -5,4 +5,6 @@ LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) noinst_LIBRARIES = libbts.a libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \ rsl.c vty.c paging.c measurement.c amr.c lchan.c \ - load_indication.c pcu_sock.c handover.c msg_utils.c + load_indication.c pcu_sock.c handover.c msg_utils.c \ + load_indication.c pcu_sock.c handover.c msg_utils.c \ + tx_power.c diff --git a/src/common/bts.c b/src/common/bts.c index 878770af..6cbafd1f 100644 --- a/src/common/bts.c +++ b/src/common/bts.c @@ -120,7 +120,9 @@ int bts_init(struct gsm_bts *bts) /* initialize bts data structure */ llist_for_each_entry(trx, &bts->trx_list, list) { + struct trx_power_params *tpp = &trx->power_params; int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; int k; @@ -130,6 +132,10 @@ int bts_init(struct gsm_bts *bts) INIT_LLIST_HEAD(&lchan->dl_tch_queue); } } + /* Default values for the power adjustments */ + tpp->ramp.max_initial_pout_mdBm = to_mdB(23); + tpp->ramp.step_size_mdB = to_mdB(2); + tpp->ramp.step_interval_sec = 1; } osmo_rtp_init(tall_bts_ctx); diff --git a/src/common/tx_power.c b/src/common/tx_power.c new file mode 100644 index 00000000..e2bb9ed7 --- /dev/null +++ b/src/common/tx_power.c @@ -0,0 +1,273 @@ +/* Transmit Power computation */ + +/* (C) 2014 by Harald Welte <laforge@gnumonks.org> + * + * 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 <limits.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/tx_power.h> + +static int get_pa_drive_level_mdBm(const struct power_amp *pa, + int desired_p_out_mdBm, unsigned int arfcn) +{ + if (arfcn > ARRAY_SIZE(pa->calib.gain_mdB)) + return INT_MIN; + + /* FIXME: temperature compensation */ + + return desired_p_out_mdBm - pa->calib.gain_mdB[arfcn]; +} + +/* maximum output power of the system */ +int get_p_max_out_mdBm(struct gsm_bts_trx *trx) +{ + struct trx_power_params *tpp = &trx->power_params; + /* Add user gain, internal and external PA gain to TRX output power */ + return tpp->trx_p_max_out_mdBm + tpp->user_gain_mdB + + tpp->pa.nominal_gain_mdB + tpp->user_pa.nominal_gain_mdB; +} + +/* nominal output power, i.e. OML-reduced maximum output power */ +int get_p_nominal_mdBm(struct gsm_bts_trx *trx) +{ + /* P_max_out subtracted by OML maximum power reduction IE */ + return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red); +} + +/* calculate the target total output power required, reduced by both + * OML and RSL, but ignoring the attenutation required for power ramping and + * thermal management */ +int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) +{ + /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */ + return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2); +} +int get_p_target_mdBm_lchan(struct gsm_lchan *lchan) +{ + return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power); +} + +/* calculate the actual total output power required, taking into account the + * attenutation required for power ramping but not thermal management */ +int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* P_target subtracted by ramp attenuation */ + return p_target_mdBm - tpp->ramp.attenuation_mdB; +} + +/* calculate the effective total output power required, taking into account the + * attenutation required for power ramping and thermal management */ +int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* P_target subtracted by ramp attenuation */ + return p_target_mdBm - tpp->ramp.attenuation_mdB - tpp->thermal_attenuation_mdB; +} + +/* calculate effect TRX output power required, taking into account the + * attenuations required for power ramping and thermal management */ +int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; + unsigned int arfcn = trx->arfcn; + + /* P_actual subtracted by any bulk gaion added by the user */ + p_actual_mdBm = get_p_eff_mdBm(trx, p_target_mdBm) - tpp->user_gain_mdB; + + /* determine input drive level required at input to user PA */ + user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_actual_mdBm, arfcn); + + /* determine input drive level required at input to internal PA */ + pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); + + /* internal PA input drive level is TRX output power */ + return pa_drvlvl_mdBm; +} + +/* calculate target TRX output power required, ignoring the + * attenuations required for power ramping but not thermal management */ +int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) +{ + struct trx_power_params *tpp = &trx->power_params; + int p_target_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; + unsigned int arfcn = trx->arfcn; + + /* P_target subtracted by any bulk gaion added by the user */ + p_target_mdBm = get_p_target_mdBm(trx, bs_power_ie) - tpp->user_gain_mdB; + + /* determine input drive level required at input to user PA */ + user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_target_mdBm, arfcn); + + /* determine input drive level required at input to internal PA */ + pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); + + /* internal PA input drive level is TRX output power */ + return pa_drvlvl_mdBm; +} +int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan) +{ + return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power); +} + + +/* output power ramping code */ + +/* The idea here is to avoid a hard switch from 0 to 100, but to actually + * slowly and gradually ramp up or down the power. This is needed on the + * one hand side to avoid very fast dynamic load changes towards the PA power + * supply, but is also needed in order to avoid a DoS by too many subscriber + * attempting to register at the same time. Rather, grow the cell slowly in + * radius than start with the full raduis at once. */ + +static int we_are_ramping_up(struct gsm_bts_trx *trx) +{ + struct trx_power_params *tpp = &trx->power_params; + + if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm) + return 1; + else + return 0; +} + +static void power_ramp_do_step(struct gsm_bts_trx *trx, int first); + +/* timer call-back for the ramp tumer */ +static void power_ramp_timer_cb(void *_trx) +{ + struct gsm_bts_trx *trx = _trx; + struct trx_power_params *tpp = &trx->power_params; + int p_trxout_eff_mdBm; + + /* compute new actual total output power (= minus ramp attenuation) */ + tpp->p_total_cur_mdBm = get_p_actual_mdBm(trx, tpp->p_total_tgt_mdBm); + + /* compute new effective (= minus ramp and thermal attenuation) TRX output required */ + p_trxout_eff_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); + + LOGP(DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, " + "ramp_att=%d, therm_att=%d, user_gain=%d)\n", + tpp->p_total_cur_mdBm, tpp->p_total_tgt_mdBm, + tpp->ramp.attenuation_mdB, tpp->thermal_attenuation_mdB, + tpp->user_gain_mdB); + + LOGP(DL1C, LOGL_INFO, + "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm); + + /* Instruct L1 to apply new effective TRX output power required */ + bts_model_change_power(trx, p_trxout_eff_mdBm); + + /* and do another step... */ + power_ramp_do_step(trx, 0); +} + +static void power_ramp_do_step(struct gsm_bts_trx *trx, int first) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* we had finished in last loop iteration */ + if (!first && tpp->ramp.attenuation_mdB == 0) + return; + + if (we_are_ramping_up(trx)) { + /* ramp up power -> ramp down attenuation */ + tpp->ramp.attenuation_mdB -= tpp->ramp.step_size_mdB; + if (tpp->ramp.attenuation_mdB <= 0) { + /* we are done */ + tpp->ramp.attenuation_mdB = 0; + } + } else { + /* ramp down power -> ramp up attenuation */ + tpp->ramp.attenuation_mdB += tpp->ramp.step_size_mdB; + if (tpp->ramp.attenuation_mdB >= 0) { + /* we are done */ + tpp->ramp.attenuation_mdB = 0; + } + } + + /* schedule timer for the next step */ + tpp->ramp.step_timer.data = trx; + tpp->ramp.step_timer.cb = power_ramp_timer_cb; + osmo_timer_schedule(&tpp->ramp.step_timer, tpp->ramp.step_interval_sec, 0); +} + + +int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* The input to this function is the actual desired output power, i.e. + * the maximum total system power subtracted by OML as well as RSL + * reductions */ + + LOGP(DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d)\n", + tpp->p_total_cur_mdBm, p_total_tgt_mdBm); + + if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) { + LOGP(DL1C, LOGL_ERROR, "Asked to ramp power up to " + "%d mdBm, which exceeds P_max_out (%d)\n", + p_total_tgt_mdBm, get_p_nominal_mdBm(trx)); + return -ERANGE; + } + + /* Cancel any pending request */ + osmo_timer_del(&tpp->ramp.step_timer); + + /* set the new target */ + tpp->p_total_tgt_mdBm = p_total_tgt_mdBm; + + if (we_are_ramping_up(trx)) { + if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) { + LOGP(DL1C, LOGL_INFO, + "target_power(%d) is below max.initial power\n", + tpp->p_total_tgt_mdBm); + /* new setting is below the maximum initial output + * power, so we can directly jump to this level */ + tpp->p_total_cur_mdBm = tpp->p_total_tgt_mdBm; + tpp->ramp.attenuation_mdB = 0; + power_ramp_timer_cb(trx); + } else { + /* We need to step it up. Start from the current value */ + /* Set attenuation to cause no power change right now */ + tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; + + /* start with the firsrt step */ + power_ramp_do_step(trx, 1); + } + } else { + /* Set ramp attenuation to negative value, and increse that by + * steps until it reaches 0 */ + tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; + + /* start with the firsrt step */ + power_ramp_do_step(trx, 1); + } + + return 0; +} diff --git a/src/common/vty.c b/src/common/vty.c index ea38ac95..a2f2fb37 100644 --- a/src/common/vty.c +++ b/src/common/vty.c @@ -186,7 +186,19 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) bts_model_config_write_bts(vty, bts); llist_for_each_entry(trx, &bts->trx_list, list) { + struct trx_power_params *tpp = &trx->power_params; vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); + + if (trx->power_params.user_gain_mdB) + vty_out(vty, " user-gain %u mdB%s", + tpp->user_gain_mdB, VTY_NEWLINE); + vty_out(vty, " power-ramp max-initinal-pout %d mdBm%s", + tpp->ramp.max_initial_pout_mdBm, VTY_NEWLINE); + vty_out(vty, " power-ramp step-size %d mdBm%s", + tpp->ramp.step_size_mdB, VTY_NEWLINE); + vty_out(vty, " power-ramp step-interval %d%s", + tpp->ramp.step_interval_sec, VTY_NEWLINE); + bts_model_config_write_trx(vty, trx); } } @@ -384,6 +396,71 @@ DEFUN(cfg_bts_agch_queue_mgmt_default, return CMD_SUCCESS; } +#define DB_DBM_STR \ + "Unit is dB (decibels)\n" \ + "Unit is mdB (milli-decibels, or rather 1/10000 bel)\n" + +static int parse_mdbm(const char *valstr, const char *unit) +{ + int val = atoi(valstr); + + if (!strcmp(unit, "dB") || !strcmp(unit, "dBm")) + return val * 1000; + else + return val; +} + +DEFUN(cfg_trx_user_gain, + cfg_trx_user_gain_cmd, + "user-gain <-100000-100000> (dB|mdB)", + "Inform BTS about additional, user-provided gain or attenuation at TRX output\n" + "Value of user-provided external gain(+)/attenuation(-)\n" DB_DBM_STR) +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.user_gain_mdB = parse_mdbm(argv[0], argv[1]); + + return CMD_SUCCESS; +} + +#define PR_STR "Power-Ramp settings" +DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd, + "power-ramp max-initial <0-100000> (dBm|mdBm)", + PR_STR "Maximum initial power\n" + "Value\n" DB_DBM_STR) +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.ramp.max_initial_pout_mdBm = + parse_mdbm(argv[0], argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_pr_step_size, cfg_trx_pr_step_size_cmd, + "power-ramp step-size <1-100000> (dB|mdB)", + PR_STR "Power increase by step\n" + "Step size\n" DB_DBM_STR) +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.ramp.step_size_mdB = + parse_mdbm(argv[0], argv[1]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd, + "power-ramp step-interval <1-100>", + PR_STR "Power increase by step\n" + "Step time in seconds\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.ramp.step_interval_sec = atoi(argv[0]); + return CMD_SUCCESS; +} + + /* ====================================================================== * SHOW @@ -563,6 +640,11 @@ int bts_vty_init(const struct log_info *cat) install_node(&trx_node, config_write_dummy); install_default(TRX_NODE); + install_element(TRX_NODE, &cfg_trx_user_gain_cmd); + install_element(TRX_NODE, &cfg_trx_pr_max_initial_cmd); + install_element(TRX_NODE, &cfg_trx_pr_step_size_cmd); + install_element(TRX_NODE, &cfg_trx_pr_step_interval_cmd); + install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd); return 0; |