osmo-bts/src/common/tx_power.c

320 lines
11 KiB
C

/* 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>
#include <osmo-bts/bts_trx.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.delta_mdB))
return INT_MIN;
/* FIXME: temperature compensation */
return desired_p_out_mdBm - pa->nominal_gain_mdB - pa->calib.delta_mdB[arfcn];
}
/* maximum output power of the system */
int get_p_max_out_mdBm(const struct gsm_bts_trx *trx)
{
const 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(const 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 attenuation required for power ramping and
* thermal management */
int get_p_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red)
{
/* Pn subtracted by RSL BS Power Recudtion (in 1 dB steps) */
return get_p_nominal_mdBm(trx) - to_mdB(bs_power_red);
}
int get_p_target_mdBm_lchan(const struct gsm_lchan *lchan)
{
return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current);
}
/* calculate the actual total output power required, taking into account the
* attenuation required for power ramping but not thermal management */
int get_p_actual_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm)
{
const 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
* attenuation required for power ramping and thermal management */
int get_p_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm)
{
const 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(const struct gsm_bts_trx *trx, int p_target_mdBm)
{
const 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 gain 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(const struct gsm_bts_trx *trx, uint8_t bs_power_red)
{
const 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 gain added by the user */
p_target_mdBm = get_p_target_mdBm(trx, bs_power_red) - 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(const struct gsm_lchan *lchan)
{
return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current);
}
/* 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 radius at once. */
static int we_are_ramping_up(const struct gsm_bts_trx *trx)
{
const 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 timer */
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);
LOGPTRX(trx, 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);
LOGPTRX(trx, 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);
}
/* BTS model call-back once one a call to bts_model_change_power()
* completes, indicating actual L1 transmit power */
void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm)
{
struct trx_power_params *tpp = &trx->power_params;
int p_trxout_should_mdBm;
p_trxout_should_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm);
/* for now we simply write an error message, but in the future
* we might use the value (again) as part of our math? */
if (p_trxout_cur_mdBm != p_trxout_should_mdBm) {
LOGPTRX(trx, DL1C, LOGL_ERROR, "bts_model notifies us of %d mdBm TRX "
"output power. However, it should be %d mdBm!\n",
p_trxout_cur_mdBm, p_trxout_should_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) {
if (tpp->ramp.compl_cb)
tpp->ramp.compl_cb(trx);
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, ramp_compl_cb_t ramp_compl_cb)
{
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 */
LOGPTRX(trx, DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d%s)\n",
tpp->p_total_cur_mdBm, p_total_tgt_mdBm, bypass ? ", bypass" : "");
if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) {
LOGPTRX(trx, 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 */
power_ramp_abort(trx);
/* set the new target */
tpp->p_total_tgt_mdBm = p_total_tgt_mdBm;
tpp->ramp.compl_cb = ramp_compl_cb;
if (we_are_ramping_up(trx)) {
if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) {
LOGPTRX(trx, DL1C, LOGL_INFO,
"target_power (%d mdBm) is below or equal to 'power ramp max-initial' power (%d mdBm)\n",
tpp->p_total_tgt_mdBm, tpp->ramp.max_initial_pout_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, shortcutting to max-initial. */
/* Set attenuation to cause no power change right now */
if (tpp->p_total_cur_mdBm + (int)tpp->ramp.step_size_mdB < tpp->ramp.max_initial_pout_mdBm)
tpp->p_total_cur_mdBm = tpp->ramp.max_initial_pout_mdBm - tpp->ramp.step_size_mdB;
tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
/* start with the first step */
power_ramp_do_step(trx, 1);
}
} else {
/* Set ramp attenuation to negative value, and increase that by
* steps until it reaches 0 */
tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
/* start with the first step */
power_ramp_do_step(trx, 1);
}
return 0;
}
/* Cancel any pending request */
void power_ramp_abort(struct gsm_bts_trx *trx)
{
osmo_timer_del(&trx->power_params.ramp.step_timer);
}
/* determine the initial transceiver output power at start-up time */
int power_ramp_initial_power_mdBm(const struct gsm_bts_trx *trx)
{
const struct trx_power_params *tpp = &trx->power_params;
int pout_mdBm;
/* this is the maximum initial output on the antenna connector
* towards the antenna */
pout_mdBm = tpp->ramp.max_initial_pout_mdBm;
/* use this as input to compute transceiver board power
* (reflecting gains in internal/external amplifiers */
return get_p_trxout_eff_mdBm(trx, pout_mdBm);
}