aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-octphy/l1_oml.c
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2015-09-06 16:04:32 +0200
committerHarald Welte <laforge@gnumonks.org>2016-01-16 17:23:10 +0100
commitb92100ad36f40d3125ff945fbd38aece873d1718 (patch)
tree3723ca47d14219f3ab78f24a3e219e0dbfaca1c4 /src/osmo-bts-octphy/l1_oml.c
parente9f12acbeb5a369282719f8e0deecc88034a5488 (diff)
Add support for Octasic OCTSDR-2G GSM PHY
This adds support for a new PHY to OsmoBTS, the Octasic OCTSDR-2G PHY. This is a proprietary GSM PHY running on a familty of Octasic DSPs.
Diffstat (limited to 'src/osmo-bts-octphy/l1_oml.c')
-rw-r--r--src/osmo-bts-octphy/l1_oml.c1399
1 files changed, 1399 insertions, 0 deletions
diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c
new file mode 100644
index 00000000..68e6cf1c
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_oml.c
@@ -0,0 +1,1399 @@
+/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015 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 <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>
+
+/* 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,
+ [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[4].rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[4]);
+ 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:
+ 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:
+ 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 gsm_bts_trx *trx, 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_lchan *lchan;
+ uint8_t sapi;
+ uint8_t direction;
+ uint8_t status;
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar);
+ OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr);
+
+ 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));
+ return -1;
+ }
+
+ sapi_queue_dispatch(lchan, ar->Header.ulReturnCode);
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+ 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 = lchan->ts->trx->nr;
+ 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 gsm_bts_trx *trx, 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_ts *ts;
+ struct gsm_lchan *lchan;
+
+ 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");
+ exit(-1);
+ }
+
+ OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr);
+ ts = &trx->ts[pcr->TrxId.byTrxId];
+ /* 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);
+ }
+
+ msgb_free(resp);
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+ sapi_queue_dispatch(lchan, pcr->Header.ulReturnCode);
+
+ return 0;
+}
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+ 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->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 octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+ 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 gsm_bts_trx *trx, 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_lchan *lchan;
+ struct sapi_cmd *cmd;
+ uint8_t status;
+
+ mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr);
+ OSMO_ASSERT(ldr->TrxId.byTrxId == trx->nr);
+
+ 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 octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+ 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->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);
+}
+
+static int 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 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);
+}
+
+static int lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ lchan->ciph_state = 0; /* FIXME: do this in common *.c */
+ 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 octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx);
+ 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 gsm_bts_trx *trx, 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;
+
+ 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;
+}
+
+int l1if_enable_events(struct gsm_bts_trx *trx)
+{
+ struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+ 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));
+ 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, enable_events_compl_cb, 0);
+}
+
+/* call-back once the TRX_OPEN_CID response arrives */
+static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or =
+ (tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h;
+
+ mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or);
+
+ OSMO_ASSERT(or->TrxId.byTrxId == trx->nr);
+
+ 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);
+
+ struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+ 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);
+
+ /* 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 octphy_hdl *fl1h = trx_octphy_hdl(trx);
+ 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 = fl1h->config.rf_port_index;
+ oc->TrxId.byTrxId = trx->nr;
+ oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn);
+ oc->Config.usArfcn = trx->arfcn;
+ oc->Config.usTsc = trx->bts->bsic & 0x7;
+ oc->Config.usBcchArfcn = trx->bts->c0->arfcn;
+ oc->RfConfig.ulRxGainDb = fl1h->config.rx_gain_db;
+ /* FIXME: compute this based on nominal transmit power, etc. */
+ oc->RfConfig.ulTxAttndB = fl1h->config.tx_atten_db;
+
+ 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);
+
+ mOCTVC1_GSM_MSG_TRX_OPEN_CMD_SWAP(oc);
+
+ return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL);
+}
+
+static int trx_close_all_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car =
+ (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h;
+
+ mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car);
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+int l1if_trx_close_all(struct gsm_bts *bts)
+{
+ struct octphy_hdl *fl1h = trx_octphy_hdl(bts->c0);
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac;
+
+ cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *)
+ msgb_put(msg, sizeof(*cac));
+ l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID);
+
+ mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac);
+
+ return l1if_req_compl(fl1h, msg, trx_close_all_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_trx_close_all(trx->bts);
+
+ return l1if_trx_open(trx);
+}
+
+/***********************************************************************
+ * PHYSICAL CHANNE ACTIVATION
+ ***********************************************************************/
+
+static int pchan_act_compl_cb(struct gsm_bts_trx *trx, 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_ts *ts;
+ struct gsm_abis_mo *mo;
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar);
+ ts_nr = ar->PchId.byTimeslotNb;
+
+ OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr);
+ 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");
+ 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 octphy_hdl *fl1h = trx_octphy_hdl(ts->trx);
+ 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 = ts->trx->nr;
+ 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:
+ 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);
+ struct octphy_hdl *fl1h = trx_octphy_hdl(trx);
+ l1if_activate_rf(fl1h, 1);
+ }
+ return 0;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ /* TODO: implement this properly */
+ /* blindly accept all state changes */
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ struct octphy_hdl *fl1 = trx_octphy_hdl(trx);
+ return l1if_activate_rf(fl1, 0);
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ /* FIXME: close only one TRX */
+ return l1if_trx_close_all(trx->bts);
+}
+
+
+/* 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;
+}