osmo-bts/src/osmo-bts-octphy/l1_oml.c

1705 lines
48 KiB
C

/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */
/* Copyright (c) 2014 Octasic Inc. All rights reserved.
* Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org>
*
* based on a copy of osmo-bts-sysmo/l1_oml.c, which is
* Copyright (C) 2011 by Harald Welte <laforge@gnumonks.org>
* Copyright (C) 2013-2014 by Holger Hans Peter Freyther
*
* 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 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 <stdint.h>
#include <errno.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/amr.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/l1sap.h>
#include "l1_if.h"
#include "l1_oml.h"
#include "l1_utils.h"
#include "octphy_hw_api.h"
#include "btsconfig.h"
#include <octphy/octvc1/octvc1_rc2string.h>
#include <octphy/octvc1/gsm/octvc1_gsm_api_swap.h>
#include <octphy/octvc1/gsm/octvc1_gsm_default.h>
#include <octphy/octvc1/gsm/octvc1_gsm_id.h>
#include <octphy/octvc1/main/octvc1_main_default.h>
#include <octphy/octvc1/main/octvc1_main_version.h>
/* Map OSMOCOM logical channel type to OctPHY Logical channel type */
static tOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM pchan_to_logChComb[_GSM_PCHAN_MAX] =
{
[GSM_PCHAN_NONE] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY,
[GSM_PCHAN_CCCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH,
[GSM_PCHAN_CCCH_SDCCH4] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_SACCHC4,
[GSM_PCHAN_TCH_F] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF,
[GSM_PCHAN_TCH_H] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH,
[GSM_PCHAN_SDCCH8_SACCH8C] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_SACCHC8,
// TODO - watch out below two!!!
[GSM_PCHAN_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF,
[GSM_PCHAN_TCH_F_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF,
#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4
[GSM_PCHAN_CCCH_SDCCH4_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4,
#endif
#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8
[GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8,
#endif
[GSM_PCHAN_UNKNOWN] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY
};
enum sapi_cmd_type {
SAPI_CMD_ACTIVATE,
SAPI_CMD_CONFIG_CIPHERING,
SAPI_CMD_CONFIG_LOGCH_PARAM,
SAPI_CMD_SACCH_REL_MARKER,
SAPI_CMD_REL_MARKER,
SAPI_CMD_DEACTIVATE,
};
struct sapi_cmd {
struct llist_head entry;
tOCTVC1_GSM_SAPI_ENUM sapi;
tOCTVC1_GSM_DIRECTION_ENUM dir;
enum sapi_cmd_type type;
int (*callback) (struct gsm_lchan * lchan, int status);
};
struct sapi_dir {
tOCTVC1_GSM_SAPI_ENUM sapi;
tOCTVC1_GSM_DIRECTION_ENUM dir;
};
static const struct sapi_dir ccch_sapis[] = {
{cOCTVC1_GSM_SAPI_ENUM_FCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_BCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_RACH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
};
static const struct sapi_dir tchf_sapis[] = {
{cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
};
static const struct sapi_dir tchh_sapis[] = {
{cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
};
static const struct sapi_dir sdcch_sapis[] = {
{cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
};
static const struct sapi_dir cbch_sapis[] = {
{cOCTVC1_GSM_SAPI_ENUM_CBCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
/* Does the CBCH really have a SACCH in Downlink */
{cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
};
static const struct sapi_dir pdtch_sapis[] = {
{cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
{cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
};
struct lchan_sapis {
const struct sapi_dir *sapis;
uint32_t num_sapis;
};
static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
[GSM_LCHAN_SDCCH] = {
.sapis = sdcch_sapis,
.num_sapis = ARRAY_SIZE(sdcch_sapis),
},
[GSM_LCHAN_TCH_F] = {
.sapis = tchf_sapis,
.num_sapis = ARRAY_SIZE(tchf_sapis),
},
[GSM_LCHAN_TCH_H] = {
.sapis = tchh_sapis,
.num_sapis = ARRAY_SIZE(tchh_sapis),
},
[GSM_LCHAN_CCCH] = {
.sapis = ccch_sapis,
.num_sapis = ARRAY_SIZE(ccch_sapis),
},
[GSM_LCHAN_PDTCH] = {
.sapis = pdtch_sapis,
.num_sapis = ARRAY_SIZE(pdtch_sapis),
},
[GSM_LCHAN_CBCH] = {
.sapis = cbch_sapis,
.num_sapis = ARRAY_SIZE(cbch_sapis),
},
};
static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
extern uint8_t rach_detected_LA_g;
extern uint8_t rach_detected_Other_g;
static int opstart_compl(struct gsm_abis_mo *mo)
{
/* TODO: Send NACK in case of error! */
/* Set to Operational State: Enabled */
oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
/* hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
mo->obj_inst.ts_nr == 0) {
struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
LCHAN_REL_ACT_OML;
lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
if (cbch) {
cbch->rel_act_kind = LCHAN_REL_ACT_OML;
lchan_activate(cbch);
}
}
/* Send OPSTART ack */
return oml_mo_opstart_ack(mo);
}
static
tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM lchan_to_GsmL1_SubCh_t(const struct gsm_lchan
* lchan)
{
switch (lchan->ts->pchan) {
case GSM_PCHAN_CCCH_SDCCH4:
case GSM_PCHAN_CCCH_SDCCH4_CBCH:
if (lchan->type == GSM_LCHAN_CCCH)
return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL;
/* fall-through */
case GSM_PCHAN_TCH_H:
case GSM_PCHAN_SDCCH8_SACCH8C:
case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
return (tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM) lchan->nr;
case GSM_PCHAN_NONE:
case GSM_PCHAN_CCCH:
case GSM_PCHAN_TCH_F:
case GSM_PCHAN_PDCH:
case GSM_PCHAN_UNKNOWN:
default:
return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL;
}
return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL;
}
static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config)
{
/* common for the SIGN, V1 and EFR: */
int i;
p_Config->byCmiPhase = 0;
p_Config->byInitRate = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
/* 4 AMR active codec set */
for (i = 0; i < cOCTVC1_GSM_RATE_LIST_SIZE; i++)
p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
}
static void lchan2lch_par(struct gsm_lchan *lchan,
tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config)
{
struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
struct gsm48_multi_rate_conf *mr_conf =
(struct gsm48_multi_rate_conf *)amr_mrc->gsm48_ie;
int j;
LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
/* we have to set some TCH payload type even if we don't
* know yet what codec we will use later on */
if (lchan->type == GSM_LCHAN_TCH_F) {
clear_amr_params(p_Config);
}
break;
case GSM48_CMODE_SPEECH_V1:
clear_amr_params(p_Config);
break;
case GSM48_CMODE_SPEECH_EFR:
clear_amr_params(p_Config);
break;
case GSM48_CMODE_SPEECH_AMR:
p_Config->byCmiPhase = 1; /* FIXME? */
p_Config->byInitRate =
(tOCTVC1_GSM_AMR_CODEC_MODE_ENUM)
amr_get_initial_mode(lchan);
/* initialize to clean state */
for (j = 0; j < cOCTVC1_GSM_RATE_LIST_SIZE; j++)
p_Config->abyRate[j] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
j = 0;
if (mr_conf->m4_75)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_4_75;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m5_15)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_15;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m5_90)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_90;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m6_70)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_6_70;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m7_40)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_40;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m7_95)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_95;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m10_2)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_10_2;
if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
break;
if (mr_conf->m12_2)
p_Config->abyRate[j++] =
cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_12_2;
break;
case GSM48_CMODE_DATA_14k5:
case GSM48_CMODE_DATA_12k0:
case GSM48_CMODE_DATA_6k0:
case GSM48_CMODE_DATA_3k6:
LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
gsm_lchan_name(lchan));
break;
}
}
/***********************************************************************
* CORE SAPI QUEUE HANDLING
***********************************************************************/
static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status);
static void sapi_queue_send(struct gsm_lchan *lchan);
static void sapi_clear_queue(struct llist_head *queue)
{
struct sapi_cmd *next, *tmp;
llist_for_each_entry_safe(next, tmp, queue, entry) {
llist_del(&next->entry);
talloc_free(next);
}
}
static int lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *ar =
(tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h;
struct gsm_bts_trx *trx;
struct gsm_lchan *lchan;
uint8_t sapi;
uint8_t direction;
uint8_t status;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar);
trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
lchan = get_lchan_by_lchid(trx, &ar->LchId);
sapi = ar->LchId.bySAPI;
direction = ar->LchId.byDirection;
LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
gsm_lchan_name(lchan),
get_value_string(octphy_l1sapi_names, sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(octphy_dir_names, direction));
if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) {
LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s\n",
get_value_string(octphy_l1sapi_names, sapi));
status = LCHAN_SAPI_S_ERROR;
} else {
status = LCHAN_SAPI_S_ASSIGNED;
}
switch (direction) {
case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS:
lchan->sapis_dl[sapi] = status;
break;
case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS:
lchan->sapis_ul[sapi] = status;
break;
default:
LOGP(DL1C, LOGL_ERROR, "Unknown direction %d\n",
ar->LchId.byDirection);
break;
}
if (llist_empty(&lchan->sapi_cmds)) {
LOGP(DL1C, LOGL_ERROR,
"%s Got activation confirmation with empty queue\n",
gsm_lchan_name(lchan));
goto err;
}
sapi_queue_dispatch(lchan, ar->Header.ulReturnCode);
err:
msgb_free(resp);
return 0;
}
static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac;
lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *)
msgb_put(msg, sizeof(*lac));
l1if_fill_msg_hdr(&lac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID);
lac->TrxId.byTrxId = pinst->u.octphy.trx_id;
lac->LchId.byTimeslotNb = lchan->ts->nr;
lac->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan);
lac->LchId.bySAPI = cmd->sapi;
lac->LchId.byDirection = cmd->dir;
lac->Config.byTimingAdvance = lchan->rqd_ta;
lac->Config.byBSIC = lchan->ts->trx->bts->bsic;
lchan2lch_par(lchan, &lac->Config);
mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac);
LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (%s ",
gsm_lchan_name(lchan),
get_value_string(octphy_l1sapi_names, cmd->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(octphy_dir_names, cmd->dir));
return l1if_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
}
static tOCTVC1_GSM_CIPHERING_ID_ENUM rsl2l1_ciph[] = {
[0] = cOCTVC1_GSM_CIPHERING_ID_ENUM_UNUSED,
[1] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_0,
[2] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_1,
[3] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_2,
[4] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_3
};
static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *pcr =
(tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *) resp->l2h;
struct gsm_bts_trx *trx;
struct gsm_bts_trx_ts *ts;
struct gsm_lchan *lchan;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP_SWAP(pcr);
if (pcr->Header.ulReturnCode != cOCTVC1_RC_OK) {
LOGP(DL1C, LOGL_ERROR, "Error: Cipher Request Failed!\n\n");
LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n");
msgb_free(resp);
exit(-1);
}
trx = trx_by_l1h(fl1, pcr->TrxId.byTrxId);
OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr);
ts = &trx->ts[pcr->PchId.byTimeslotNb];
/* for some strange reason the response does not tell which
* sub-channel, only th request contains this information :( */
lchan = &ts->lchan[(unsigned long) data];
/* TODO: This state machine should be shared accross BTS models? */
switch (lchan->ciph_state) {
case LCHAN_CIPH_RX_REQ:
lchan->ciph_state = LCHAN_CIPH_RX_CONF;
break;
case LCHAN_CIPH_RX_CONF_TX_REQ:
lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
break;
case LCHAN_CIPH_RXTX_REQ:
lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
break;
case LCHAN_CIPH_NONE:
break;
default:
LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
}
if (llist_empty(&lchan->sapi_cmds)) {
LOGP(DL1C, LOGL_ERROR,
"%s Got ciphering conf with empty queue\n",
gsm_lchan_name(lchan));
goto err;
}
sapi_queue_dispatch(lchan, pcr->Header.ulReturnCode);
err:
msgb_free(resp);
return 0;
}
static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *pcc;
pcc = (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *)
msgb_put(msg, sizeof(*pcc));
l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID);
pcc->TrxId.byTrxId = pinst->u.octphy.trx_id;
pcc->PchId.byTimeslotNb = lchan->ts->nr;
pcc->ulSubchannelNb = lchan_to_GsmL1_SubCh_t(lchan);
pcc->ulDirection = cmd->dir;
pcc->Config.ulCipherId = rsl2l1_ciph[lchan->encr.alg_id];
memcpy(pcc->Config.abyKey, lchan->encr.key, lchan->encr.key_len);
LOGP(DL1C, LOGL_INFO, "%s SET_CIPHERING (ALG=%u %s)\n",
gsm_lchan_name(lchan), pcc->Config.ulCipherId,
get_value_string(octphy_dir_names, pcc->ulDirection));
mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD_SWAP(pcc);
/* we have to save the lchan number in this strange way, as the
* PHY does not return the ulSubchannelNr in the response to
* this command */
return l1if_req_compl(fl1h, msg, set_ciph_compl_cb, (void *)(unsigned long) lchan->nr);
}
/**
* Queue and possible execute a SAPI command. Return 1 in case the command was
* already executed and 0 in case if it was only put into the queue
*/
static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
int start = llist_empty(&lchan->sapi_cmds);
llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
if (!start)
return 0;
sapi_queue_send(lchan);
return 1;
}
static int mph_info_chan_confirm(struct gsm_lchan *lchan,
enum osmo_mph_info_type type, uint8_t cause)
{
struct osmo_phsap_prim l1sap;
memset(&l1sap, 0, sizeof(l1sap));
osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
NULL);
l1sap.u.info.type = type;
l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
l1sap.u.info.u.act_cnf.cause = cause;
return l1sap_up(lchan->ts->trx, &l1sap);
}
static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
{
/* FIXME: Error handling. There is no NACK... */
if (status != cOCTVC1_RC_OK && lchan->state == LCHAN_S_REL_REQ) {
LOGP(DL1C, LOGL_ERROR,
"%s is now broken. Stopping the release.\n",
gsm_lchan_name(lchan));
lchan_set_state(lchan, LCHAN_S_BROKEN);
sapi_clear_queue(&lchan->sapi_cmds);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
return -1;
}
if (!llist_empty(&lchan->sapi_cmds))
return 0;
/* Don't send an REL ACK on SACCH deactivate */
if (lchan->state != LCHAN_S_REL_REQ)
return 0;
lchan_set_state(lchan, LCHAN_S_NONE);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
return 0;
}
static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
{
struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
cmd->sapi = sapi;
cmd->dir = dir;
cmd->type = SAPI_CMD_DEACTIVATE;
cmd->callback = sapi_deactivate_cb;
return queue_sapi_command(lchan, cmd);
}
/*
* Release the SAPI if it was allocated. E.g. the SACCH might be already
* deactivated or during a hand-over the TCH was not allocated yet.
*/
static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
{
/* check if we should schedule a release */
if (dir == cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS) {
if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
return 0;
lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
} else if (dir == cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS) {
if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
return 0;
lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
}
/* now schedule the command and maybe dispatch it */
return enqueue_sapi_deact_cmd(lchan, sapi, dir);
}
static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
{
struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
int i, res;
res = 0;
/* The order matters.. the Facch needs to be released first */
for (i = s4l->num_sapis - 1; i >= 0; i--) {
/* Stop the alive timer once we deactivate the SCH */
if (s4l->sapis[i].sapi == cOCTVC1_GSM_SAPI_ENUM_SCH)
osmo_timer_del(&fl1h->alive_timer);
/* Release if it was allocated */
res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
}
/* nothing was queued */
if (res == 0) {
LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
gsm_lchan_name(lchan));
lchan_set_state(lchan, LCHAN_S_BROKEN);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
}
return res;
}
static int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *ldr =
(tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h;
struct gsm_bts_trx *trx;
struct gsm_lchan *lchan;
struct sapi_cmd *cmd;
uint8_t status;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr);
trx = trx_by_l1h(fl1, ldr->TrxId.byTrxId);
lchan = get_lchan_by_lchid(trx, &ldr->LchId);
LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
gsm_lchan_name(lchan),
get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(octphy_dir_names, ldr->LchId.byDirection));
if (ldr->Header.ulReturnCode == cOCTVC1_RC_OK) {
DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI),
ldr->LchId.byTimeslotNb);
status = LCHAN_SAPI_S_NONE;
} else {
LOGP(DL1C, LOGL_ERROR,
"Error deactivating L1 SAPI %s on TS %u\n",
get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI),
ldr->LchId.byTimeslotNb);
status = LCHAN_SAPI_S_ERROR;
}
switch (ldr->LchId.byDirection) {
case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS:
lchan->sapis_dl[ldr->LchId.bySAPI] = status;
break;
case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS:
lchan->sapis_ul[ldr->LchId.bySAPI] = status;
break;
}
if (llist_empty(&lchan->sapi_cmds)) {
LOGP(DL1C, LOGL_ERROR,
"%s Got de-activation confirmation with empty queue\n",
gsm_lchan_name(lchan));
goto err;
}
cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
if (cmd->sapi != ldr->LchId.bySAPI ||
cmd->dir != ldr->LchId.byDirection ||
cmd->type != SAPI_CMD_DEACTIVATE) {
LOGP(DL1C, LOGL_ERROR,
"%s Confirmation mismatch (%d, %d) (%d, %d)\n",
gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
ldr->LchId.bySAPI, ldr->LchId.byDirection);
goto err;
}
sapi_queue_dispatch(lchan, status);
err:
msgb_free(resp);
return 0;
}
static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *ldc;
ldc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *)
msgb_put(msg, sizeof(*ldc));
l1if_fill_msg_hdr(&ldc->Header, msg, fl1h,cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID);
ldc->TrxId.byTrxId = pinst->u.octphy.trx_id;
ldc->LchId.byTimeslotNb = lchan->ts->nr;
ldc->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan);
ldc->LchId.byDirection = cmd->dir;
ldc->LchId.bySAPI = cmd->sapi;
mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(ldc);
LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
gsm_lchan_name(lchan),
get_value_string(octphy_l1sapi_names, cmd->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(octphy_dir_names, cmd->dir));
return l1if_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
}
/**
* Execute the first SAPI command of the queue. In case of the markers
* this method is re-entrant so we need to make sure to remove a command
* from the list before calling a function that will queue a command.
*
* \return 0 in case no Gsm Request was sent, 1 otherwise
*/
static int sapi_queue_exeute(struct gsm_lchan *lchan)
{
int res = 0;
struct sapi_cmd *cmd;
cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
switch (cmd->type) {
case SAPI_CMD_ACTIVATE:
mph_send_activate_req(lchan, cmd);
res = 1;
break;
case SAPI_CMD_CONFIG_CIPHERING:
mph_send_config_ciphering(lchan, cmd);
res = 1;
break;
case SAPI_CMD_CONFIG_LOGCH_PARAM:
/* TODO: Mode modif not supported by OctPHY currently */
/* mph_send_config_logchpar(lchan, cmd); */
res = 1;
break;
case SAPI_CMD_SACCH_REL_MARKER:
llist_del(&cmd->entry);
talloc_free(cmd);
res =
check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH,
cOCTVC1_GSM_ID_DIRECTION_ENUM_TX_BTS_MS);
res |=
check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH,
cOCTVC1_GSM_ID_DIRECTION_ENUM_RX_BTS_MS);
break;
case SAPI_CMD_REL_MARKER:
llist_del(&cmd->entry);
talloc_free(cmd);
res = lchan_deactivate_sapis(lchan);
break;
case SAPI_CMD_DEACTIVATE:
res = mph_send_deactivate_req(lchan, cmd);
res = 1;
break;
default:
LOGP(DL1C, LOGL_NOTICE,
"Unimplemented command type %d\n", cmd->type);
llist_del(&cmd->entry);
talloc_free(cmd);
res = 0;
abort();
break;
}
return res;
}
static void sapi_queue_send(struct gsm_lchan *lchan)
{
int res;
do {
res = sapi_queue_exeute(lchan);
} while (res == 0 && !llist_empty(&lchan->sapi_cmds));
}
static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
{
int end;
struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
struct sapi_cmd, entry);
llist_del(&cmd->entry);
end = llist_empty(&lchan->sapi_cmds);
if (cmd->callback)
cmd->callback(lchan, status);
talloc_free(cmd);
if (end || llist_empty(&lchan->sapi_cmds)) {
LOGP(DL1C, LOGL_NOTICE,
"%s End of queue encountered. Now empty? %d\n",
gsm_lchan_name(lchan), llist_empty(&lchan->sapi_cmds));
return;
}
sapi_queue_send(lchan);
}
/* we regularly check if the L1 is still sending us primitives.
if not, we simply stop the BTS program (and be re-spawned) */
static void alive_timer_cb(void *data)
{
struct octphy_hdl *fl1h = data;
if (fl1h->alive_prim_cnt == 0) {
LOGP(DL1C, LOGL_FATAL, "L1 is no longer sending primitives!\n");
exit(23);
}
fl1h->alive_prim_cnt = 0;
osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
}
/***********************************************************************
* RSL DEACTIVATE SACCH
***********************************************************************/
static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
{
struct sapi_cmd *cmd;
/* remember we need to check if the SACCH is allocated */
cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
cmd->type = SAPI_CMD_SACCH_REL_MARKER;
queue_sapi_command(lchan, cmd);
}
int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
{
enqueue_sacch_rel_marker(lchan);
return 0;
}
int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
{
/* Only de-activate the SACCH if the lchan is active */
if (lchan->state != LCHAN_S_ACTIVE)
return 0;
return bts_model_lchan_deactivate_sacch(lchan);
}
/***********************************************************************
* RSL CHANNEL RELEASE
***********************************************************************/
static void enqueue_rel_marker(struct gsm_lchan *lchan)
{
struct sapi_cmd *cmd;
/* remember we need to release all active SAPIs */
cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
cmd->type = SAPI_CMD_REL_MARKER;
queue_sapi_command(lchan, cmd);
}
int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
{
lchan_set_state(lchan, LCHAN_S_REL_REQ);
enqueue_rel_marker(lchan);
return 0;
}
int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
{
/* A duplicate RF Release Request, ignore it */
if (lchan->state == LCHAN_S_REL_REQ)
return 0;
lchan_deactivate(lchan);
return 0;
}
/***********************************************************************
* SET CIPHERING
***********************************************************************/
static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
{
struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
cmd->dir = dir;
cmd->type = SAPI_CMD_CONFIG_CIPHERING;
queue_sapi_command(lchan, cmd);
}
int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink)
{
int dir;
// ignore the request when the channel is not active
if (lchan->state != LCHAN_S_ACTIVE)
return -1;
if (dir_downlink)
dir = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS;
else
dir = cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS;
enqueue_sapi_ciphering_cmd(lchan, dir);
return 0;
}
/***********************************************************************
* RSL MODE MODIFY
***********************************************************************/
/* Mode modify is currently not supported by OctPHY */
static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir)
{
struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
cmd->dir = dir;
cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
queue_sapi_command(lchan, cmd);
}
/* Mode modify is currently not supported by OctPHY */
static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
{
enqueue_sapi_logchpar_cmd(lchan, direction);
return 0;
}
/* Mode modify is currently not supported by OctPHY */
int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
{
if (lchan->state != LCHAN_S_ACTIVE)
return -1;
/* channel mode, encryption and/or multirate have changed */
/* update multi-rate config */
tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS);
tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS);
/* FIXME: update encryption */
return 0;
}
/***********************************************************************
* LCHAN / SAPI ACTIVATION
***********************************************************************/
static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
{
if (status != cOCTVC1_RC_OK) {
lchan_set_state(lchan, LCHAN_S_BROKEN);
sapi_clear_queue(&lchan->sapi_cmds);
mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE,
RSL_ERR_EQUIPMENT_FAIL);
return -1;
}
if (!llist_empty(&lchan->sapi_cmds))
return 0;
if (lchan->state != LCHAN_S_ACT_REQ)
return 0;
lchan_set_state(lchan, LCHAN_S_ACTIVE);
mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
return 0;
}
static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
{
struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
cmd->sapi = sapi;
cmd->dir = dir;
cmd->type = SAPI_CMD_ACTIVATE;
cmd->callback = sapi_activate_cb;
queue_sapi_command(lchan, cmd);
}
int lchan_activate(struct gsm_lchan *lchan)
{
struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
unsigned int i;
lchan_set_state(lchan, LCHAN_S_ACT_REQ);
DEBUGP(DL1C, "lchan_act called\n");
if (!llist_empty(&lchan->sapi_cmds))
LOGP(DL1C, LOGL_ERROR,
"%s Trying to activate lchan, but commands in queue\n",
gsm_lchan_name(lchan));
for (i = 0; i < s4l->num_sapis; i++) {
int sapi = s4l->sapis[i].sapi;
int dir = s4l->sapis[i].dir;
if (sapi == cOCTVC1_GSM_SAPI_ENUM_SCH) {
/* once we activate the SCH, we should get MPH-TIME.ind */
fl1h->alive_timer.cb = alive_timer_cb;
fl1h->alive_timer.data = fl1h;
fl1h->alive_prim_cnt = 0;
osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
}
enqueue_sapi_act_cmd(lchan, sapi, dir);
}
lchan_init_lapdm(lchan);
return 0;
}
int l1if_rsl_chan_act(struct gsm_lchan *lchan)
{
lchan_activate(lchan);
return 0;
}
static int enable_events_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *mser =
(tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *) resp->l2h;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP_SWAP(mser);
LOGP(DL1C, LOGL_INFO, "Rx ENABLE-EVT-REC.resp\n");
msgb_free(resp);
return 0;
}
static int disable_events_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *mser =
(tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *) resp->l2h;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP_SWAP(mser);
LOGP(DL1C, LOGL_INFO, "Rx DISABLE-EVT-REC.resp\n");
msgb_free(resp);
return 0;
}
int l1if_enable_events(struct gsm_bts_trx *trx)
{
struct phy_instance *pinst = trx_phy_instance(trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *mse;
mse = (tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *)
msgb_put(msg, sizeof(*mse));
mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD_DEF(mse);
l1if_fill_msg_hdr(&mse->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CID);
mse->ulEvtActiveFlag = cOCT_TRUE;
mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD_SWAP(mse);
LOGP(DL1C, LOGL_INFO, "Tx ENABLE-EVT-REC.req\n");
return l1if_req_compl(fl1h, msg, disable_events_compl_cb, 0);
}
int l1if_disable_events(struct gsm_bts_trx *trx)
{
struct phy_instance *pinst = trx_phy_instance(trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *mse;
mse = (tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *)
msgb_put(msg, sizeof(*mse));
mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD_DEF(mse);
l1if_fill_msg_hdr(&mse->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CID);
mse->ulEvtActiveFlag = cOCT_FALSE;
mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD_SWAP(mse);
LOGP(DL1C, LOGL_INFO, "Tx DISABLE-EVT-REC.req\n");
return l1if_req_compl(fl1h, msg, disable_events_compl_cb, 0);
}
#define talloc_replace(dst, ctx, src) \
do { \
if (dst) \
talloc_free(dst); \
dst = talloc_strdup(ctx, (const char *) src); \
} while (0)
static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
{
tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr =
(tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *) resp->l2h;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP_SWAP(aisr);
LOGP(DL1C, LOGL_INFO, "Rx APP-INFO-SYSTEM.resp (platform='%s', version='%s')\n",
aisr->szPlatform, aisr->szVersion);
#if OCTPHY_MULTI_TRX == 1
LOGP(DL1C, LOGL_INFO, "Note: compiled with multi-trx support.\n");
#else
LOGP(DL1C, LOGL_INFO, "Note: compiled without multi-trx support.\n");
#endif
talloc_replace(fl1h->info.system.platform, fl1h, aisr->szPlatform);
talloc_replace(fl1h->info.system.version, fl1h, aisr->szVersion);
msgb_free(resp);
return 0;
}
int l1if_check_app_sys_version(struct gsm_bts_trx *trx)
{
struct phy_instance *pinst = trx_phy_instance(trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *ais;
ais = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *)
msgb_put(msg, sizeof(*ais));
mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_DEF(ais);
l1if_fill_msg_hdr(&ais->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID);
mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_SWAP(ais);
LOGP(DL1C, LOGL_INFO, "Tx APP-INFO-SYSTEM.req\n");
return l1if_req_compl(fl1h, msg, app_info_sys_compl_cb, 0);
}
static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp,
void *data)
{
char ver_hdr[32];
tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air =
(tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h;
sprintf(ver_hdr, "%02i.%02i.%02i-B%i", cOCTVC1_MAIN_VERSION_MAJOR,
cOCTVC1_MAIN_VERSION_MINOR, cOCTVC1_MAIN_VERSION_MAINTENANCE,
cOCTVC1_MAIN_VERSION_BUILD);
mOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP_SWAP(air);
LOGP(DL1C, LOGL_INFO,
"Rx APP-INFO.resp (name='%s', desc='%s', ver='%s', ver_hdr='%s')\n",
air->szName, air->szDescription, air->szVersion, ver_hdr);
/* Bail if dsp firmware does not match up the header version info */
if (strcmp(air->szVersion, ver_hdr) != 0) {
LOGP(DL1C, LOGL_ERROR,
"Invalid header-file / dsp-firmware combination, exiting...\n");
exit(1);
}
talloc_replace(fl1h->info.app.name, fl1h, air->szName);
talloc_replace(fl1h->info.app.description, fl1h, air->szDescription);
talloc_replace(fl1h->info.app.version, fl1h, air->szVersion);
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
msgb_free(resp);
return 0;
}
int l1if_check_app_version(struct gsm_bts_trx *trx)
{
struct phy_instance *pinst = trx_phy_instance(trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *ai;
ai = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *) msgb_put(msg, sizeof(*ai));
mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_DEF(ai);
l1if_fill_msg_hdr(&ai->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID);
mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_SWAP(ai);
LOGP(DL1C, LOGL_INFO, "Tx APP-INFO.req\n");
return l1if_req_compl(fl1h, msg, app_info_compl_cb, 0);
}
static int trx_close_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *car =
(tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *) resp->l2h;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_GSM_MSG_TRX_CLOSE_RSP_SWAP(car);
LOGP(DL1C, LOGL_INFO, "Rx TRX-CLOSE.conf(%u)\n", car->TrxId.byTrxId);
msgb_free(resp);
return 0;
}
static int trx_close(struct gsm_bts_trx *trx)
{
struct phy_instance *pinst = trx_phy_instance(trx);
struct phy_link *plink = pinst->phy_link;
struct octphy_hdl *fl1h = plink->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *cac;
cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *)
msgb_put(msg, sizeof(*cac));
l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_GSM_MSG_TRX_CLOSE_CID);
cac->TrxId.byTrxId = pinst->u.octphy.trx_id;
LOGP(DL1C, LOGL_INFO, "Tx TRX-CLOSE.req(%u)\n", cac->TrxId.byTrxId);
mOCTVC1_GSM_MSG_TRX_CLOSE_CMD_SWAP(cac);
return l1if_req_compl(fl1h, msg, trx_close_cb, NULL);
}
/* call-back once the TRX_OPEN_CID response arrives */
static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
{
struct gsm_bts_trx *trx;
tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or =
(tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or);
trx = trx_by_l1h(fl1h, or->TrxId.byTrxId);
LOGP(DL1C, LOGL_INFO, "TRX-OPEN.resp(trx=%u) = %s\n",
trx->nr, octvc1_rc2string(or->Header.ulReturnCode));
/* FIXME: check for ulReturnCode == OK */
if (or->Header.ulReturnCode != cOCTVC1_RC_OK) {
LOGP(DL1C, LOGL_ERROR, "TRX-OPEN failed: %s\n",
octvc1_rc2string(or->Header.ulReturnCode));
msgb_free(resp);
exit(1);
}
msgb_free(resp);
opstart_compl(&trx->mo);
octphy_hw_get_pcb_info(fl1h);
octphy_hw_get_rf_port_info(fl1h, 0);
octphy_hw_get_rf_ant_rx_config(fl1h, 0, 0);
octphy_hw_get_rf_ant_tx_config(fl1h, 0, 0);
octphy_hw_get_rf_ant_rx_config(fl1h, 0, 1);
octphy_hw_get_rf_ant_tx_config(fl1h, 0, 1);
octphy_hw_get_clock_sync_info(fl1h);
fl1h->opened = 1;
/* Temporary fix for enabling events after TRX Close + Reopen */
return l1if_enable_events(trx);
}
int l1if_trx_open(struct gsm_bts_trx *trx)
{
/* putting it all together */
struct phy_instance *pinst = trx_phy_instance(trx);
struct phy_link *plink = pinst->phy_link;
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_GSM_MSG_TRX_OPEN_CMD *oc;
oc = (tOCTVC1_GSM_MSG_TRX_OPEN_CMD *) msgb_put(msg, sizeof(*oc));
l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_GSM_MSG_TRX_OPEN_CID);
oc->ulRfPortIndex = plink->u.octphy.rf_port_index;
oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn);
oc->Config.usArfcn = trx->arfcn;
#if OCTPHY_MULTI_TRX == 1
if (pinst->u.octphy.trx_id)
oc->Config.usCentreArfcn = plink->u.octphy.center_arfcn;
else {
oc->Config.usCentreArfcn = trx->arfcn;
plink->u.octphy.center_arfcn = trx->arfcn;
}
oc->Config.usBcchArfcn = trx->bts->c0->arfcn;
#endif
oc->Config.usTsc = trx->bts->bsic & 0x7;
oc->RfConfig.ulRxGainDb = plink->u.octphy.rx_gain_db;
/* FIXME: compute this based on nominal transmit power, etc. */
if (plink->u.octphy.tx_atten_flag) {
oc->RfConfig.ulTxAttndB = plink->u.octphy.tx_atten_db;
} else {
/* Take the Tx Attn received in set radio attribures
* x4 is for the value in db */
oc->RfConfig.ulTxAttndB = (trx->max_power_red) << 2;
}
#if OCTPHY_USE_ANTENNA_ID == 1
oc->RfConfig.ulTxAntennaId = plink->u.octphy.tx_ant_id;
oc->RfConfig.ulRxAntennaId = plink->u.octphy.rx_ant_id;
#endif
#if OCTPHY_MULTI_TRX == 1
LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, "
"center=%u, tsc=%u, rx_gain=%u, tx_atten=%u)\n",
oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn,
oc->Config.usCentreArfcn, oc->Config.usTsc, oc->RfConfig.ulRxGainDb,
oc->RfConfig.ulTxAttndB);
#else
LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, "
"tsc=%u, rx_gain=%u, tx_atten=%u)\n",
oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn,
oc->Config.usTsc, oc->RfConfig.ulRxGainDb,
oc->RfConfig.ulTxAttndB);
#endif
mOCTVC1_GSM_MSG_TRX_OPEN_CMD_SWAP(oc);
return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL);
}
uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx)
{
return 0;
}
static int trx_init(struct gsm_bts_trx *trx)
{
if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
ARRAY_SIZE(trx_rqd_attr))) {
/* HACK: spec says we need to decline, but openbsc
* doesn't deal with this very well */
return oml_mo_opstart_ack(&trx->mo);
/* return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); */
}
l1if_check_app_version(trx);
l1if_check_app_sys_version(trx);
return l1if_trx_open(trx);
}
/***********************************************************************
* PHYSICAL CHANNE ACTIVATION
***********************************************************************/
static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
{
tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar =
(tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h;
uint8_t ts_nr;
struct gsm_bts_trx *trx;
struct gsm_bts_trx_ts *ts;
struct gsm_abis_mo *mo;
/* in a completion call-back, we take msgb ownership and must
* release it before returning */
mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar);
trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
ts_nr = ar->PchId.byTimeslotNb;
OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts));
ts = &trx->ts[ts_nr];
LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n",
ts->trx->nr, ts->nr, ts->pchan,
octvc1_rc2string(ar->Header.ulReturnCode));
if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) {
LOGP(DL1C, LOGL_ERROR,
"PCHAN-ACT failed: %s\n\n",
octvc1_rc2string(ar->Header.ulReturnCode));
LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n");
msgb_free(resp);
exit(-1);
}
trx = ts->trx;
mo = &trx->ts[ar->PchId.byTimeslotNb].mo;
msgb_free(resp);
return opstart_compl(mo);
}
static int ts_connect(struct gsm_bts_trx_ts *ts)
{
struct phy_instance *pinst = trx_phy_instance(ts->trx);
struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
struct msgb *msg = l1p_msgb_alloc();
tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc =
(tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc;
oc = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) msgb_put(msg, sizeof(*oc));
l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID);
oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
oc->PchId.byTimeslotNb = ts->nr;
oc->ulChannelType = pchan_to_logChComb[ts->pchan];
/* TODO: how should we know the payload type here? Also, why
* would the payload type have to be the same for both halves of
* a TCH/H ? */
switch (oc->ulChannelType) {
case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF:
case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF:
oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE;
break;
case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH:
oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE;
break;
}
LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.req(trx=%u, ts=%u, chcomb=%u)\n",
ts->trx->nr, ts->nr, ts->pchan);
mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc);
return l1if_req_compl(fl1h, msg, pchan_act_compl_cb, NULL);
}
/***********************************************************************
* BTS MODEL CALLBACKS
***********************************************************************/
int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
{
/* TODO: How to do this ? */
return 0;
}
int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo,
const uint8_t * attr_ids, unsigned int num_attr_ids)
{
unsigned int i;
if (!mo->nm_attr)
return 0;
for (i = 0; i < num_attr_ids; i++) {
if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
return 0;
}
return 1;
}
int bts_model_oml_estab(struct gsm_bts *bts)
{
int i;
for (i = 0; i < bts->num_trx; i++) {
struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i);
l1if_activate_rf(trx, 1);
}
return 0;
}
int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
void *obj, uint8_t adm_state)
{
int rc;
struct gsm_bts_trx *trx;
struct phy_instance *pinst;
struct octphy_hdl *fl1h;
switch (mo->obj_class) {
case NM_OC_RADIO_CARRIER:
trx = ((struct gsm_bts_trx *)obj);
pinst = trx_phy_instance(trx);
fl1h = pinst->phy_link->u.octphy.hdl;
if (mo->procedure_pending) {
LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
"pending procedure on TRX %d\n", trx->nr);
return 0;
}
mo->procedure_pending = 1;
switch (adm_state) {
case NM_STATE_LOCKED:
pinst->u.octphy.trx_locked = 1;
/* Stop heartbeat check */
osmo_timer_del(&fl1h->alive_timer);
bts_model_trx_deact_rf(trx);
/* Close TRX */
rc = bts_model_trx_close(trx);
if (rc != 0) {
LOGP(DL1C, LOGL_ERROR,
"Cannot close TRX %d, it is already closed.\n",
trx->nr);
}
break;
case NM_STATE_UNLOCKED:
if (pinst->u.octphy.trx_locked) {
pinst->u.octphy.trx_locked = 0;
l1if_activate_rf(trx, 1);
}
break;
default:
break;
}
mo->procedure_pending = 0;
break;
default:
/* blindly accept all state changes */
break;
}
mo->nm_state.administrative = adm_state;
return oml_mo_statechg_ack(mo);
}
int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
{
return l1if_activate_rf(trx, 0);
}
int bts_model_trx_close(struct gsm_bts_trx *trx)
{
/* disable events */
l1if_disable_events(trx);
/* FIXME: close only one TRX */
return trx_close(trx);
}
/* callback from OML */
int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
struct tlv_parsed *old_attr,
struct tlv_parsed *new_attr, void *obj)
{
/* FIXME: check if the attributes are valid */
return 0;
}
/* callback from OML */
int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
struct tlv_parsed *new_attr, int kind, void *obj)
{
if (kind == NM_OC_RADIO_CARRIER) {
struct gsm_bts_trx *trx = obj;
/*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */
power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
}
return oml_fom_ack_nack(msg, 0);
}
/* callback from OML */
int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj)
{
int rc = -1;
switch (mo->obj_class) {
case NM_OC_RADIO_CARRIER:
rc = trx_init(obj);
break;
case NM_OC_CHANNEL:
rc = ts_connect(obj);
break;
case NM_OC_BTS:
case NM_OC_SITE_MANAGER:
case NM_OC_BASEB_TRANSC:
case NM_OC_GPRS_NSE:
case NM_OC_GPRS_CELL:
case NM_OC_GPRS_NSVC:
oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
rc = oml_mo_opstart_ack(mo);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
}
return rc;
}
int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
{
#warning "Implement bts_model_change_power based on TRX_MODIFY_RF_CID"
return 0;
}
int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
{
return -ENOTSUP;
}
int bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
enum gsm_phys_chan_config as_pchan)
{
return -ENOTSUP;
}