/* * layer 1 primitive handling and interface * * Copyright (C) 2013 Andreas Eversberg * Copyright (C) 2015 Alexander Chemeris * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "l1_if.h" #include "trx_if.h" #include "trx_provision_fsm.h" #define RF_DISABLED_mdB to_mdB(-10) static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = { [GSM_PCHAN_NONE] = 8, [GSM_PCHAN_CCCH] = 4, [GSM_PCHAN_CCCH_SDCCH4] = 5, [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 5, [GSM_PCHAN_TCH_F] = 1, [GSM_PCHAN_TCH_H] = 3, [GSM_PCHAN_SDCCH8_SACCH8C] = 7, [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 7, [GSM_PCHAN_PDCH] = 13, /* [GSM_PCHAN_TCH_F_PDCH] not needed here, see trx_set_ts_as_pchan() */ [GSM_PCHAN_UNKNOWN] = 0, }; enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type) { int i; for (i = 0; i < _GSM_PCHAN_MAX; i++) { if (transceiver_chan_types[i] == type) return (enum gsm_phys_chan_config) i; } return GSM_PCHAN_UNKNOWN; } struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst) { struct trx_l1h *l1h; l1h = talloc_zero(tall_ctx, struct trx_l1h); l1h->phy_inst = pinst; l1h->provision_fi = osmo_fsm_inst_alloc(&trx_prov_fsm, l1h, l1h, LOGL_INFO, NULL); OSMO_ASSERT(osmo_fsm_inst_update_id_f_sanitize(l1h->provision_fi, '-', phy_instance_name(pinst)) == 0); trx_if_init(l1h); return l1h; } int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { lchan->rel_act_kind = LCHAN_REL_ACT_RSL; /* FIXME: perform whatever is needed (if any) to set proper PCH/AGCH allocation according to 3GPP TS 44.018 Table 10.5.2.11.1 using num_agch(lchan->ts->trx, "TRX L1"); function */ return 0; } /* set lchan inactive */ lchan_set_state(lchan, LCHAN_S_NONE); return trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_DEDIC, false); } int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_SACCH, false); } int l1if_trx_start_power_ramp(struct gsm_bts_trx *trx, ramp_compl_cb_t ramp_compl_cb) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; if (l1h->config.forced_max_power_red == -1) return power_ramp_start(trx, get_p_nominal_mdBm(trx), 0, ramp_compl_cb); else return power_ramp_start(trx, get_p_max_out_mdBm(trx) - to_mdB(l1h->config.forced_max_power_red), 1, ramp_compl_cb); } /* Sets the nominal power, in dB */ void l1if_trx_set_nominal_power(struct gsm_bts_trx *trx, int nominal_power) { struct phy_instance *pinst = trx_phy_instance(trx); bool nom_pwr_changed = trx->nominal_power != nominal_power; trx->nominal_power = nominal_power; trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); /* If we receive ultra-low nominal Tx power (<0dBm), make sure to update where we are */ trx->power_params.p_total_cur_mdBm = OSMO_MIN(trx->power_params.p_total_cur_mdBm, trx->power_params.trx_p_max_out_mdBm); /* If TRX is not yet powered, delay ramping until it's ON */ if (!nom_pwr_changed || !pinst->phy_link->u.osmotrx.powered || trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) return; /* We are already ON and we got new information about nominal power, so * let's make sure we adapt the tx power to it */ l1if_trx_start_power_ramp(trx, NULL); } static void l1if_setpower_att_cb(struct trx_l1h *l1h, int power_att_db, int rc) { struct phy_instance *pinst = l1h->phy_inst; struct gsm_bts_trx *trx = pinst->trx; LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_setpower_att_cb(power_att_db=%d, rc=%d)\n", power_att_db, rc); power_trx_change_compl(trx, get_p_max_out_mdBm(trx) - to_mdB(power_att_db)); } /* * activation/configuration/deactivation of transceiver's TRX */ /* initialize the layer1 */ static int trx_init(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; int rc; rc = osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ENABLE, (void*)(intptr_t)true); if (rc != 0) return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, (void*)(intptr_t)NM_NACK_CANT_PERFORM); /* Send OPSTART ack */ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); } /* Deact RF on transceiver */ int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; return trx_if_cmd_rfmute(l1h, true); } /* deactivate transceiver */ void bts_model_trx_close(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CLOSE, NULL); /* Set to Operational State: Disabled */ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); } void bts_model_abis_close(struct gsm_bts *bts) { /* Go into shutdown state deactivating transceivers until Abis link * becomes up again */ bts_shutdown_ext(bts, "Abis close", false); } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { /* we always implement the power control loop in osmo-bts software, as * there is no automatism in the underlying osmo-trx */ return 0; } /* set bts attributes */ static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr) { struct phy_instance *pinst = trx_phy_instance(bts->c0); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; uint8_t bsic = bts->bsic; struct gsm_bts_trx *trx; /* ARFCN for C0 is assigned during Set BTS Attr, see oml.c */ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ARFCN, (void *)(intptr_t)pinst->trx->arfcn); llist_for_each_entry(trx, &bts->trx_list, list) { pinst = trx_phy_instance(trx); l1h = pinst->u.osmotrx.hdl; osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_BSIC, (void*)(intptr_t)bsic); } return 0; } /* set trx attributes */ static uint8_t trx_set_trx(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; struct phy_link *plink = pinst->phy_link; uint16_t arfcn = trx->arfcn; /* ARFCN for C0 is assigned during Set BTS Attr, see oml.c */ if (trx != trx->bts->c0) osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ARFCN, (void *)(intptr_t)arfcn); /* Begin to ramp up the power if power reduction is set by OML and TRX is already running. Otherwise skip, power ramping will be started after TRX is running */ if (plink->u.osmotrx.powered && l1h->config.forced_max_power_red == -1 && trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) power_ramp_start(pinst->trx, get_p_nominal_mdBm(pinst->trx), 0, NULL); return 0; } /* set ts attributes */ static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan) { struct phy_instance *pinst = trx_phy_instance(ts->trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; uint8_t tn = ts->nr; uint8_t slottype; int rc; /* ignore disabled slots */ if (!(l1h->config.slotmask & (1 << tn))) return NM_NACK_RES_NOTAVAIL; /* set physical channel. For dynamic timeslots, the caller should have * decided on a more specific PCHAN type already. */ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); rc = trx_sched_set_pchan(ts, pchan); if (rc) return NM_NACK_RES_NOTAVAIL; /* activate lchan for CCCH */ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 || pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) { ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); } slottype = transceiver_chan_types[pchan]; struct trx_prov_ev_cfg_ts_data data = { .tn = tn, .slottype = slottype }; if (ts->tsc_set != 0) { /* On TRXC we use 3GPP compliant numbering, so +1 */ data.tsc_set = ts->tsc_set + 1; data.tsc_val = ts->tsc; data.tsc_valid = true; } osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_TS, &data); return 0; } static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) { enum gsm_phys_chan_config pchan; /* For dynamic timeslots, pick the pchan type that should currently be * active. This should only be called during init, PDCH transitions * will call trx_set_ts_as_pchan() directly. */ switch (ts->pchan) { case GSM_PCHAN_TCH_F_PDCH: OSMO_ASSERT((ts->flags & TS_F_PDCH_PENDING_MASK) == 0); pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH : GSM_PCHAN_TCH_F; break; case GSM_PCHAN_OSMO_DYN: OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want); pchan = ts->dyn.pchan_is; break; default: pchan = ts->pchan; break; } return trx_set_ts_as_pchan(ts, pchan); } /* * primitive handling */ /* enable ciphering */ static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink) { /* ciphering already enabled in both directions */ if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) return -EINVAL; if (!downlink) { /* set uplink */ trx_sched_set_cipher(lchan, chan_nr, false); lchan->ciph_state = LCHAN_CIPH_RX_CONF; } else { /* set downlink and also set uplink, if not already */ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) trx_sched_set_cipher(lchan, chan_nr, false); trx_sched_set_cipher(lchan, chan_nr, true); lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; } return 0; } static int mph_info_chan_confirm(struct gsm_bts_trx *trx, uint8_t chan_nr, 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 = chan_nr; l1sap.u.info.u.act_cnf.cause = cause; return l1sap_up(trx, &l1sap); } int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn) { struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_INDICATION, NULL); l1sap.u.info.type = PRIM_INFO_TIME; l1sap.u.info.u.time_ind.fn = fn; if (!bts->c0) return -EINVAL; return l1sap_up(bts->c0, &l1sap); } /* primitive from common part */ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { struct msgb *msg = l1sap->oph.msg; uint8_t chan_nr; int rc = 0; struct gsm_lchan *lchan; switch (OSMO_PRIM_HDR(&l1sap->oph)) { case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ return trx_sched_ph_data_req(trx, l1sap); case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ return trx_sched_tch_req(trx, l1sap); case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): switch (l1sap->u.info.type) { case PRIM_INFO_ACT_CIPH: chan_nr = l1sap->u.info.u.ciph_req.chan_nr; lchan = get_lchan_by_chan_nr(trx, chan_nr); if (l1sap->u.info.u.ciph_req.uplink) l1if_set_ciphering(lchan, chan_nr, 0); if (l1sap->u.info.u.ciph_req.downlink) l1if_set_ciphering(lchan, chan_nr, 1); break; case PRIM_INFO_ACTIVATE: case PRIM_INFO_DEACTIVATE: case PRIM_INFO_MODIFY: chan_nr = l1sap->u.info.u.act_req.chan_nr; lchan = get_lchan_by_chan_nr(trx, chan_nr); if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { if ((chan_nr & 0xE0) == 0x80) { LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot activate" " channel %s\n", rsl_chan_nr_str(chan_nr)); break; } /* attempt to allocate an Error Concealment Unit instance, if available */ lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan)); /* activate dedicated channel */ trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, true); /* activate associated channel */ trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, true); /* set mode */ trx_sched_set_mode(lchan->ts, chan_nr, lchan->rsl_cmode, lchan->tch_mode, lchan->tch.amr_mr.num_modes, lchan->tch.amr_mr.mode[0].mode, lchan->tch.amr_mr.mode[1].mode, lchan->tch.amr_mr.mode[2].mode, lchan->tch.amr_mr.mode[3].mode, amr_get_initial_mode(lchan), (lchan->ho.active == 1)); /* set lchan active */ lchan_set_state(lchan, LCHAN_S_ACTIVE); /* set initial ciphering */ l1if_set_ciphering(lchan, chan_nr, 0); l1if_set_ciphering(lchan, chan_nr, 1); if (lchan->encr.alg_id) lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; else lchan->ciph_state = LCHAN_CIPH_NONE; /* confirm */ mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); break; } if (l1sap->u.info.type == PRIM_INFO_MODIFY) { /* ECU for possibly new codec */ if (lchan->ecu_state) osmo_ecu_destroy(lchan->ecu_state); lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan)); /* change mode */ trx_sched_set_mode(lchan->ts, chan_nr, lchan->rsl_cmode, lchan->tch_mode, lchan->tch.amr_mr.num_modes, lchan->tch.amr_mr.mode[0].mode, lchan->tch.amr_mr.mode[1].mode, lchan->tch.amr_mr.mode[2].mode, lchan->tch.amr_mr.mode[3].mode, amr_get_initial_mode(lchan), 0); break; } /* here, type == PRIM_INFO_DEACTIVATE */ if ((chan_nr & 0xE0) == 0x80) { LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot deactivate" " channel %s\n", rsl_chan_nr_str(chan_nr)); break; } /* clear ECU state (if any) */ if (lchan->ecu_state) { osmo_ecu_destroy(lchan->ecu_state); lchan->ecu_state = NULL; } /* deactivate associated channel */ bts_model_lchan_deactivate_sacch(lchan); if (!l1sap->u.info.u.act_req.sacch_only) { /* deactivate dedicated channel */ lchan_deactivate(lchan); /* confirm only on dedicated channel */ mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); } break; default: LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", l1sap->u.info.type); rc = -EINVAL; goto done; } break; default: LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", l1sap->oph.primitive, l1sap->oph.operation); rc = -EINVAL; goto done; } done: if (msg) msgb_free(msg); return rc; } /* * oml handling */ /* 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) { struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_abis_mo *mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); struct nm_fsm_ev_setattr_data ev_data = { .msg = msg, .cause = 0, }; int rc; /* TODO: NM Object without FSM: */ switch (foh->obj_class) { case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: return oml_fom_ack_nack(ev_data.msg, ev_data.cause); } switch (foh->msg_type) { case NM_MT_SET_BTS_ATTR: ev_data.cause = trx_set_bts(obj, new_attr); break; case NM_MT_SET_RADIO_ATTR: ev_data.cause = trx_set_trx(obj); break; case NM_MT_SET_CHAN_ATTR: ev_data.cause = trx_set_ts(obj); break; } rc = osmo_fsm_inst_dispatch(mo->fi, ev_data.cause == 0 ? NM_EV_SETATTR_ACK : NM_EV_SETATTR_NACK, &ev_data); /* msgb ownsership is transferred to FSM if it received ev: */ return rc == 0 ? 1 : 0; } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { struct gsm_bts_bb_trx *bb_transc; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; int rc; switch (mo->obj_class) { case NM_OC_SITE_MANAGER: rc = osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OPSTART_ACK, NULL); break; case NM_OC_BTS: rc = osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OPSTART_ACK, NULL); break; case NM_OC_RADIO_CARRIER: /* activate transceiver */ trx = (struct gsm_bts_trx *) obj; rc = trx_init(trx); break; case NM_OC_BASEB_TRANSC: bb_transc = (struct gsm_bts_bb_trx *) obj; rc = osmo_fsm_inst_dispatch(bb_transc->mo.fi, NM_EV_OPSTART_ACK, NULL); break; case NM_OC_CHANNEL: ts = (struct gsm_bts_trx_ts *) obj; rc = osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OPSTART_ACK, NULL); break; case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); rc = oml_mo_opstart_ack(mo); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); } return rc; } static void bts_model_chg_adm_state_ramp_compl_cb(struct gsm_bts_trx *trx) { LOGPTRX(trx, DL1C, LOGL_INFO, "power ramp due to ADM STATE change finished\n"); trx->mo.procedure_pending = 0; if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) { bts_model_trx_deact_rf(trx); pcu_tx_info_ind(); } } int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { struct gsm_bts_trx *trx; struct phy_instance *pinst; struct trx_l1h *l1h; int rc = 0; switch (mo->obj_class) { case NM_OC_RADIO_CARRIER: trx = (struct gsm_bts_trx *) obj; pinst = trx_phy_instance(trx); l1h = pinst->u.osmotrx.hdl; /* Begin to ramp the power if TRX is already running. Otherwise * skip, power ramping will be started after TRX is running. * We still want to make sure to update RFMUTE status on the * other side. */ if (!pinst->phy_link->u.osmotrx.powered) { trx_if_cmd_rfmute(l1h, adm_state != NM_STATE_UNLOCKED); break; } if (mo->procedure_pending) { LOGPTRX(trx, DL1C, LOGL_INFO, "ADM change received while previous one still WIP\n"); if (mo->nm_state.administrative == NM_STATE_LOCKED && adm_state == NM_STATE_UNLOCKED) { /* Previous change was UNLOCKED->LOCKED, so we * were ramping down and we didn't mute RF * yet, so now simply skip old ramp down compl * cb, skip RF unmute and go for ramp up * directly. */ goto ramp_up; } else if (mo->nm_state.administrative == NM_STATE_UNLOCKED && adm_state == NM_STATE_LOCKED) { /* Previous change was LOCKED->UNLOCKED, so we * simply need to skip ramping up and start * ramping down instead, muting RF at the * end as usual. Fall through usual procedure * below. */ } else if (mo->nm_state.administrative == adm_state) { OSMO_ASSERT(0); } } switch (adm_state) { case NM_STATE_LOCKED: mo->procedure_pending = 1; rc = power_ramp_start(trx, RF_DISABLED_mdB, 1, bts_model_chg_adm_state_ramp_compl_cb); break; case NM_STATE_UNLOCKED: mo->procedure_pending = 1; trx_if_cmd_rfmute(l1h, false); ramp_up: rc = l1if_trx_start_power_ramp(trx, bts_model_chg_adm_state_ramp_compl_cb); if (rc == 0) { mo->nm_state.administrative = adm_state; pcu_tx_info_ind(); return oml_mo_statechg_ack(mo); } break; default: break; } break; default: break; } if (rc == 0) { mo->nm_state.administrative = adm_state; return oml_mo_statechg_ack(mo); } else return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); } int bts_model_oml_estab(struct gsm_bts *bts) { return 0; } int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; int power_att = (get_p_max_out_mdBm(trx) - p_trxout_mdBm) / 1000; return trx_if_cmd_setpower_att(l1h, power_att, l1if_setpower_att_cb); } int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) { /* no action required, signal completion right away. */ cb_ts_disconnected(ts); return 0; } void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) { int rc; LOGP(DL1C, LOGL_DEBUG, "%s bts_model_ts_connect(as_pchan=%s)\n", gsm_ts_name(ts), gsm_pchan_name(as_pchan)); rc = trx_set_ts_as_pchan(ts, as_pchan); if (rc) cb_ts_connected(ts, rc); /* cb_ts_connected will be called in l1if_setslot_cb once we receive RSP SETSLOT */ }