osmo-bts/src/common/oml.c

550 lines
16 KiB
C

/*
* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/*
* Operation and Maintainance Messages
*/
#include <errno.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <osmocore/protocol/gsm_12_21.h>
#include <osmo-bts/logging.h>
//#include <osmocom/bb/common/osmocom_data.h>
#include <osmo-bts/support.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/rtp.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/oml.h>
/*
* support
*/
/* FIXME: move this to osmocore */
const struct tlv_definition nm_att_tlvdef = {
.def = {
[NM_ATT_ABIS_CHANNEL] = { TLV_TYPE_FIXED, 3 },
[NM_ATT_ADD_INFO] = { TLV_TYPE_TL16V },
[NM_ATT_ADD_TEXT] = { TLV_TYPE_TL16V },
[NM_ATT_ADM_STATE] = { TLV_TYPE_TV },
[NM_ATT_ARFCN_LIST]= { TLV_TYPE_TL16V },
[NM_ATT_AUTON_REPORT] = { TLV_TYPE_TV },
[NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TL16V },
[NM_ATT_BCCH_ARFCN] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_BSIC] = { TLV_TYPE_TV },
[NM_ATT_BTS_AIR_TIMER] = { TLV_TYPE_TV },
[NM_ATT_CCCH_L_I_P] = { TLV_TYPE_TV },
[NM_ATT_CCCH_L_T] = { TLV_TYPE_TV },
[NM_ATT_CHAN_COMB] = { TLV_TYPE_TV },
[NM_ATT_CONN_FAIL_CRIT] = { TLV_TYPE_TL16V },
[NM_ATT_DEST] = { TLV_TYPE_TL16V },
[NM_ATT_EVENT_TYPE] = { TLV_TYPE_TV },
[NM_ATT_FILE_DATA] = { TLV_TYPE_TL16V },
[NM_ATT_FILE_ID] = { TLV_TYPE_TL16V },
[NM_ATT_FILE_VERSION] = { TLV_TYPE_TL16V },
[NM_ATT_GSM_TIME] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_HSN] = { TLV_TYPE_TV },
[NM_ATT_HW_CONFIG] = { TLV_TYPE_TL16V },
[NM_ATT_HW_DESC] = { TLV_TYPE_TL16V },
[NM_ATT_INTAVE_PARAM] = { TLV_TYPE_TV },
[NM_ATT_INTERF_BOUND] = { TLV_TYPE_FIXED, 6 },
[NM_ATT_LIST_REQ_ATTR] = { TLV_TYPE_TL16V },
[NM_ATT_MAIO] = { TLV_TYPE_TV },
[NM_ATT_MANUF_STATE] = { TLV_TYPE_TV },
[NM_ATT_MANUF_THRESH] = { TLV_TYPE_TL16V },
[NM_ATT_MANUF_ID] = { TLV_TYPE_TL16V },
[NM_ATT_MAX_TA] = { TLV_TYPE_TV },
[NM_ATT_MDROP_LINK] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_MDROP_NEXT] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_NACK_CAUSES] = { TLV_TYPE_TV },
[NM_ATT_NY1] = { TLV_TYPE_TV },
[NM_ATT_OPER_STATE] = { TLV_TYPE_TV },
[NM_ATT_OVERL_PERIOD] = { TLV_TYPE_TL16V },
[NM_ATT_PHYS_CONF] = { TLV_TYPE_TL16V },
[NM_ATT_POWER_CLASS] = { TLV_TYPE_TV },
[NM_ATT_POWER_THRESH] = { TLV_TYPE_FIXED, 3 },
[NM_ATT_PROB_CAUSE] = { TLV_TYPE_FIXED, 3 },
[NM_ATT_RACH_B_THRESH] = { TLV_TYPE_TV },
[NM_ATT_LDAVG_SLOTS] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_RAD_SUBC] = { TLV_TYPE_TV },
[NM_ATT_RF_MAXPOWR_R] = { TLV_TYPE_TV },
[NM_ATT_SITE_INPUTS] = { TLV_TYPE_TL16V },
[NM_ATT_SITE_OUTPUTS] = { TLV_TYPE_TL16V },
[NM_ATT_SOURCE] = { TLV_TYPE_TL16V },
[NM_ATT_SPEC_PROB] = { TLV_TYPE_TV },
[NM_ATT_START_TIME] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_T200] = { TLV_TYPE_FIXED, 7 },
[NM_ATT_TEI] = { TLV_TYPE_TV },
[NM_ATT_TEST_DUR] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_TEST_NO] = { TLV_TYPE_TV },
[NM_ATT_TEST_REPORT] = { TLV_TYPE_TL16V },
[NM_ATT_VSWR_THRESH] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_WINDOW_SIZE] = { TLV_TYPE_TV },
[NM_ATT_TSC] = { TLV_TYPE_TV },
[NM_ATT_SW_CONFIG] = { TLV_TYPE_TL16V },
[NM_ATT_SEVERITY] = { TLV_TYPE_TV },
[NM_ATT_GET_ARI] = { TLV_TYPE_TL16V },
[NM_ATT_HW_CONF_CHG] = { TLV_TYPE_TL16V },
[NM_ATT_OUTST_ALARM] = { TLV_TYPE_TV },
[NM_ATT_MEAS_RES] = { TLV_TYPE_TL16V },
},
};
struct osmobts_trx *get_trx_by_nr(struct osmocom_bts *bts, uint8_t trx_nr)
{
int max = sizeof(bts->trx) / sizeof(bts->trx[0]);
struct osmobts_trx *trx;
if (trx_nr >= max) {
LOGP(DOML, LOGL_NOTICE, "Indicated TRX #%d is out of range. (max #%d)\n", trx_nr, max - 1);
return NULL;
}
trx = bts->trx[trx_nr];
if (!trx) {
LOGP(DOML, LOGL_NOTICE, "Indicated TRX #%d does not exist.\n", trx_nr);
return NULL;
}
return trx;
}
struct osmobts_slot *get_slot_by_nr(struct osmobts_trx *trx, uint8_t slot_nr)
{
struct osmobts_slot *slot;
if (slot_nr >= 8) {
LOGP(DOML, LOGL_NOTICE, "Indicated Slot #%d is out of range. (max #8)\n", slot_nr);
return NULL;
}
slot = &trx->slot[slot_nr];
return slot;
}
static struct msgb *fom_msgb_alloc(void)
{
struct msgb *nmsg;
nmsg = abis_msgb_alloc(sizeof(struct abis_om_hdr));
if (!nmsg)
return NULL;
return nmsg;
}
static void fom_push_om(struct msgb *msg, uint8_t mdisc, uint8_t placement, uint8_t sequence)
{
struct abis_om_hdr *noh;
msg->l3h = msg->data;
noh = (struct abis_om_hdr *) msgb_push(msg, sizeof(*noh));
noh->mdisc = mdisc;
noh->placement = placement;
noh->sequence = sequence;
noh->length = msgb_l3len(msg);
}
static int fom_ack_nack(struct ipabis_link *link, struct msgb *msg, uint8_t cause)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
uint8_t *ie;
/* alter message type */
if (cause) {
LOGP(DOML, LOGL_NOTICE, "Sending FOM NACK with cause %d.\n", cause);
foh->msg_type += 2; /* nack */
/* add cause */
ie = msgb_put(msg, 2);
ie[0] = NM_ATT_NACK_CAUSES;
ie[1] = cause;
} else {
LOGP(DOML, LOGL_NOTICE, "Sending FOM ACK.\n");
foh->msg_type++; /* ack */
}
return abis_tx(link, msg);
}
/*
* Formatted O&M messages
*/
/* 8.3.7 sending SW Activated Report */
int oml_tx_sw_act_rep(struct ipabis_link *link, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr)
{
struct msgb *nmsg;
struct abis_om_fom_hdr *nofh;
LOGP(DOML, LOGL_INFO, "Sending SW Activated Report (%02x,%02x,%02x).\n", bts_nr, trx_nr, ts_nr);
nmsg = fom_msgb_alloc();
if (!nmsg)
return -ENOMEM;
nofh = (struct abis_om_fom_hdr *) msgb_put(nmsg, sizeof(*nofh));
nofh->msg_type = NM_MT_SW_ACTIVATED_REP;
nofh->obj_class = obj_class;
nofh->obj_inst.bts_nr = bts_nr;
nofh->obj_inst.trx_nr = trx_nr;
nofh->obj_inst.ts_nr = ts_nr;
fom_push_om(nmsg, ABIS_OM_MDISC_FOM, ABIS_OM_PLACEMENT_ONLY, 0);
abis_push_ipa(nmsg, IPA_PROTO_OML);
return abis_tx(link, nmsg);
}
/* 8.8.1 sending State Changed Event Report */
int oml_tx_state_changed(struct ipabis_link *link, uint8_t op_state, uint8_t avail_status, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr)
{
struct msgb *nmsg;
struct abis_om_fom_hdr *nofh;
uint8_t *ie;
LOGP(DOML, LOGL_INFO, "Sending state change (bts=%02x trx=%02x ts=%02x).\n", bts_nr, trx_nr, ts_nr);
nmsg = fom_msgb_alloc();
if (!nmsg)
return -ENOMEM;
nofh = (struct abis_om_fom_hdr *) msgb_put(nmsg, sizeof(*nofh));
nofh->msg_type = NM_MT_STATECHG_EVENT_REP;
nofh->obj_class = obj_class;
nofh->obj_inst.bts_nr = bts_nr;
nofh->obj_inst.trx_nr = trx_nr;
nofh->obj_inst.ts_nr = ts_nr;
/* 9.4.38 Operational State */
ie = msgb_put(nmsg, 2);
ie[0] = NM_ATT_OPER_STATE;
ie[1] = op_state;
/* 9.4.7 Availability Status */
ie = msgb_put(nmsg, 4);
ie[0] = NM_ATT_AVAIL_STATUS;
ie[1] = 0;
ie[2] = 1;
ie[3] = avail_status;
fom_push_om(nmsg, ABIS_OM_MDISC_FOM, ABIS_OM_PLACEMENT_ONLY, 0);
abis_push_ipa(nmsg, IPA_PROTO_OML);
return abis_tx(link, nmsg);
}
/* 8.6.1 Set BTS Attributes is received */
int oml_rx_set_bts_attr(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
struct tlv_parsed tp;
struct bts_support *sup = &bts_support;
LOGP(DOML, LOGL_INFO, "BSC is setting BTS attributes:\n");
tlv_parse(&tp, &nm_att_tlvdef, foh->data, msgb_l3len(msg) - sizeof(*foh), 0, 0);
/* 9.4.31 Maximum Timing Advance */
if (TLVP_PRESENT(&tp, NM_ATT_MAX_TA)) {
uint16_t *fn = (uint16_t *) TLVP_VAL(&tp, NM_ATT_START_TIME);
bts->max_ta = ntohs(*fn);
LOGP(DOML, LOGL_INFO, " Maximum TA = %d\n", bts->max_ta);
}
/* 9.4.8 BCCH ARFCN */
if (TLVP_PRESENT(&tp, NM_ATT_BCCH_ARFCN)) {
uint16_t *value = (uint16_t *) TLVP_VAL(&tp, NM_ATT_BCCH_ARFCN);
uint16_t arfcn = ntohs(*value);
LOGP(DOML, LOGL_INFO, " ARFCN = %d", bts->bcch_arfcn);
if (arfcn > 1023 || !(sup->freq_map[arfcn >> 3] & (1 << (arfcn & 0x7)))) {
LOGP(DOML, LOGL_NOTICE, "Given ARFCN %d is not supported.\n", arfcn);
return fom_ack_nack(&bts->link, msg, NM_NACK_FREQ_NOTAVAIL);
}
bts->bcch_arfcn = arfcn;
}
/* 9.4.9 BSIC */
if (TLVP_PRESENT(&tp, NM_ATT_BSIC)) {
uint8_t *bsic = (uint8_t *) TLVP_VAL(&tp, NM_ATT_BSIC);
bts->bcc = *bsic & 0x7;
bts->ncc = (*bsic >> 3) & 0x7;
LOGP(DOML, LOGL_INFO, " BCC = %d\n", bts->bcc);
LOGP(DOML, LOGL_INFO, " NCC = %d\n", bts->ncc);
}
/* 9.4.52 Starting Time */
if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) {
uint16_t *fn = (uint16_t *) TLVP_VAL(&tp, NM_ATT_START_TIME);
bts->start_time = ntohs(*fn);
LOGP(DOML, LOGL_INFO, " Starting Time = %d\n", bts->start_time);
}
return fom_ack_nack(&bts->link, msg, 0);
}
/* 8.6.2 Set Radio Attributes is received */
int oml_rx_set_radio_attr(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
struct tlv_parsed tp;
struct osmobts_trx *trx;
struct bts_support *sup = &bts_support;
trx = get_trx_by_nr(bts, foh->obj_inst.trx_nr);
if (!trx)
return fom_ack_nack(&bts->link, msg, NM_NACK_TRXNR_UNKN);
LOGP(DOML, LOGL_INFO, "BSC is setting radio attributes:\n");
tlv_parse(&tp, &nm_att_tlvdef, foh->data, msgb_l3len(msg) - sizeof(*foh), 0, 0);
/* 9.4.47 RF Max Power Reduction */
if (TLVP_PRESENT(&tp, NM_ATT_RF_MAXPOWR_R)) {
trx->rf_red = *TLVP_VAL(&tp, NM_ATT_RF_MAXPOWR_R);
LOGP(DOML, LOGL_INFO, " RF Max Power Reduction = %d\n", trx->rf_red);
} else
trx->rf_red = 0;
/* 9.4.5 ARFCN List */
if (TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) {
uint16_t *value = (uint16_t *) TLVP_VAL(&tp, NM_ATT_ARFCN_LIST);
uint16_t length = *(TLVP_VAL(&tp, NM_ATT_ARFCN_LIST) - 1);
uint16_t arfcn;
int max = (sizeof(trx->arfcn_list) / sizeof(trx->arfcn_list[0]));
int i;
if (length > max) {
LOGP(DOML, LOGL_NOTICE, "Too many ARFCN given. (max #%d)\n", max);
return fom_ack_nack(&bts->link, msg, NM_NACK_PARAM_RANGE);
}
for (i = 0; i < length; i++) {
arfcn = ntohs(*value++);
if (arfcn > 1023 || !(sup->freq_map[arfcn >> 3] & (1 << (arfcn & 0x7))))
return fom_ack_nack(&bts->link, msg, NM_NACK_FREQ_NOTAVAIL);
trx->arfcn_list[i] = arfcn;
LOGP(DOML, LOGL_INFO, " ARFCN list = %d\n", trx->arfcn_list[i]);
}
trx->arfcn_num = length;
} else
trx->arfcn_num = 0;
return fom_ack_nack(&bts->link, msg, 0);
}
/* 8.6.3 Set Channel Attributes is received */
int oml_rx_set_chan_attr(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
struct tlv_parsed tp;
struct osmobts_trx *trx;
struct osmobts_slot *slot;
struct bts_support *sup = &bts_support;
trx = get_trx_by_nr(bts, foh->obj_inst.trx_nr);
if (!trx)
return fom_ack_nack(&bts->link, msg, NM_NACK_TRXNR_UNKN);
slot = get_slot_by_nr(trx, foh->obj_inst.ts_nr);
if (!slot)
return fom_ack_nack(&bts->link, msg, NM_NACK_OBJINST_UNKN);
LOGP(DOML, LOGL_INFO, "BSC is setting channel attributes:\n");
tlv_parse(&tp, &nm_att_tlvdef, foh->data, msgb_l3len(msg) - sizeof(*foh), 0, 0);
/* 9.4.13 Channel Combination */
if (TLVP_PRESENT(&tp, NM_ATT_CHAN_COMB)) {
uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB);
if (!sup->chan_comb[comb]) {
LOGP(DOML, LOGL_NOTICE, " channel combination %d (not supported).\n", comb);
return fom_ack_nack(&bts->link, msg, NM_NACK_SPEC_IMPL_NOTSUPP);
}
LOGP(DOML, LOGL_INFO, " channel combination = %s\n", bts_support_comb_name(comb));
bts_setup_slot(slot, comb);
slot->chan_comb = comb;
}
/* 9.4.21 HSN... */
if (TLVP_PRESENT(&tp, NM_ATT_HSN)) {
LOGP(DOML, LOGL_NOTICE, "Frequency hopping not supported.\n");
return fom_ack_nack(&bts->link, msg, NM_NACK_SPEC_IMPL_NOTSUPP);
}
return fom_ack_nack(&bts->link, msg, 0);
}
/* 8.9.2 Opstart is received */
int oml_rx_opstart(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
struct osmobts_trx *trx;
struct osmobts_slot *slot;
/* site manager */
if (foh->obj_inst.bts_nr == 0xff) {
LOGP(DOML, LOGL_INFO, "BSC is sending Opstart. (Site Manager)\n");
oml_tx_state_changed(&bts->link, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, NM_OC_SITE_MANAGER, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
return fom_ack_nack(&bts->link, msg, 0);
}
#warning todo: change state
/* BTS */
if (foh->obj_inst.trx_nr == 0xff) {
LOGP(DOML, LOGL_INFO, "BSC is sending Opstart. (BTS)\n");
return fom_ack_nack(&bts->link, msg, 0);
}
/* TRX */
trx = get_trx_by_nr(bts, foh->obj_inst.trx_nr);
if (!trx)
return fom_ack_nack(&bts->link, msg, NM_NACK_TRXNR_UNKN);
if (foh->obj_inst.ts_nr == 0xff) {
LOGP(DOML, LOGL_INFO, "BSC is sending Opstart. (TRX %d)\n", trx->trx_nr);
if (trx->link.state == LINK_STATE_IDLE) {
int ret;
/* connecting TRX */
ret = abis_open(&trx->link, bts->link.ip);
if (ret <= 0) {
LOGP(DOML, LOGL_ERROR, "Failed to connect TRX.\n");
return fom_ack_nack(&bts->link, msg, NM_NACK_CANT_PERFORM);
}
}
return fom_ack_nack(&bts->link, msg, 0);
}
/* slot */
slot = get_slot_by_nr(trx, foh->obj_inst.ts_nr);
if (!slot)
return fom_ack_nack(&bts->link, msg, NM_NACK_OBJINST_UNKN);
LOGP(DOML, LOGL_INFO, "BSC is sending Opstart. (trx=%d ts=%d)\n", trx->trx_nr, slot->slot_nr);
oml_tx_state_changed(&bts->link, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, NM_OC_CHANNEL, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
return fom_ack_nack(&bts->link, msg, 0);
}
static int down_fom(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
int ret;
if (msgb_l2len(msg) < sizeof(*foh)) {
LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n");
msgb_free(msg);
return -EIO;
}
if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) {
LOGP(DOML, LOGL_INFO, "Formatted O&M with BTS %d out of range.\n", foh->obj_inst.bts_nr);
return fom_ack_nack(&bts->link, msg, NM_NACK_BTSNR_UNKN);
}
switch (foh->msg_type) {
case NM_MT_SET_BTS_ATTR:
ret = oml_rx_set_bts_attr(bts, msg);
break;
case NM_MT_SET_RADIO_ATTR:
ret = oml_rx_set_radio_attr(bts, msg);
break;
case NM_MT_SET_CHAN_ATTR:
ret = oml_rx_set_chan_attr(bts, msg);
break;
case NM_MT_OPSTART:
ret = oml_rx_opstart(bts, msg);
break;
case NM_MT_CHG_ADM_STATE:
LOGP(DOML, LOGL_INFO, "BSC is changing ADM state.\n");
ret = fom_ack_nack(&bts->link, msg, 0);
break;
default:
LOGP(DOML, LOGL_INFO, "unknown Formatted O&M msg_type 0x%02x\n",
foh->msg_type);
ret = fom_ack_nack(&bts->link, msg, NM_NACK_MSGTYPE_INVAL);
}
return ret;
}
/*
* manufacturer related messages
*/
static int down_mom(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
int ret;
if (msgb_l2len(msg) < sizeof(*foh)) {
LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n");
msgb_free(msg);
return -EIO;
}
if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) {
LOGP(DOML, LOGL_INFO, "Manufacturer O&M with BTS %d out of range.\n", foh->obj_inst.bts_nr);
return fom_ack_nack(&bts->link, msg, NM_NACK_BTSNR_UNKN);
}
switch (foh->msg_type) {
default:
LOGP(DOML, LOGL_INFO, "Manufacturer Formatted O&M msg_type 0x%02x\n",
foh->msg_type);
ret = fom_ack_nack(&bts->link, msg, NM_NACK_MSGTYPE_INVAL);
}
return ret;
}
/*
* selecting messages
*/
int down_oml(struct osmocom_bts *bts, struct msgb *msg)
{
struct abis_om_hdr *oh = msgb_l2(msg);
int ret = 0;
if (msgb_l2len(msg) < 1) {
LOGP(DOML, LOGL_NOTICE, "OML message too short\n");
msgb_free(msg);
return -EIO;
}
msg->l3h = (unsigned char *)oh + sizeof(*oh);
switch (oh->mdisc) {
case ABIS_OM_MDISC_FOM:
if (msgb_l2len(msg) < sizeof(*oh)) {
LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n");
msgb_free(msg);
ret = -EIO;
break;
}
ret = down_fom(bts, msg);
break;
case ABIS_OM_MDISC_MANUF:
if (msgb_l2len(msg) < sizeof(*oh)) {
LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n");
msgb_free(msg);
ret = -EIO;
break;
}
ret = down_mom(bts, msg);
break;
default:
LOGP(DOML, LOGL_NOTICE, "unknown OML msg_discr 0x%02x\n",
oh->mdisc);
msgb_free(msg);
ret = -EINVAL;
}
return ret;
}