/** * Messages coming back from the MSC. */ int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct osmo_msc_data *msc; struct gsm_network *net; struct gsm48_loc_area_id *lai; struct gsm48_hdr *gh; uint8_t mtype; if (msgb_l3len(msg) < sizeof(*gh)) { LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n"); return -1; } gh = (struct gsm48_hdr *) msgb_l3(msg); mtype = gh->msg_type & 0xbf; net = conn->bts->network; msc = conn->sccp_con->msc; if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) { if (msc->core_ncc != -1 || msc->core_mcc != -1) { if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) { lai = (struct gsm48_loc_area_id *) &gh->data[0]; gsm48_generate_lai(lai, net->country_code, net->network_code, conn->bts->location_area_code); } } if (conn->sccp_con->new_subscriber) send_welcome_ussd(conn); } return 0; }
static void handle_ass_compl(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct gsm48_hdr *gh; struct bsc_api *api = conn->bts->network->bsc_api; if (conn->secondary_lchan != msg->lchan) { LOGP(DMSC, LOGL_ERROR, "Assignment Compl should occur on second lchan.\n"); return; } gh = msgb_l3(msg); if (msgb_l3len(msg) - sizeof(*gh) != 1) { LOGP(DMSC, LOGL_ERROR, "Assignment Compl invalid: %lu\n", msgb_l3len(msg) - sizeof(*gh)); return; } /* swap channels */ osmo_timer_del(&conn->T10); lchan_release(conn->lchan, 0, 1); conn->lchan = conn->secondary_lchan; conn->secondary_lchan = NULL; if (is_ipaccess_bts(conn->bts) && conn->lchan->tch_mode != GSM48_CMODE_SIGN) rsl_ipacc_crcx(conn->lchan); api->assign_compl(conn, gh->data[0], lchan_to_chosen_channel(conn->lchan), conn->lchan->encr.alg_id, chan_mode_to_speech(conn->lchan)); }
/* 9.1.20 IMMEDIATE ASSIGNMENT REJECT is received */ static int imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_imm_ass_rej *ia = msgb_l3(msg); int i; struct gsm48_req_ref *req_ref; LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT:\n"); if (state != SCAN_STATE_RACH) { LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); return 0; } for (i = 0; i < 4; i++) { /* request reference */ req_ref = (struct gsm48_req_ref *) (((uint8_t *)&ia->req_ref1) + i * 4); LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT " "(ref 0x%02x)\n", req_ref->ra); if (match_ra(ms, req_ref)) { return ta_result(0xff); } } return 0; }
/* receive BCCH at RR layer */ static int bcch(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_system_information_type_header *sih = msgb_l3(msg); struct gsm48_sysinfo *s = &g_sysinfo; if (msgb_l3len(msg) != 23) { LOGP(DRR, LOGL_NOTICE, "Invalid BCCH message length\n"); return -EINVAL; } switch (sih->system_information) { case GSM48_MT_RR_SYSINFO_1: LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n"); gsm48_decode_sysinfo1(s, (struct gsm48_system_information_type_1 *) sih, msgb_l3len(msg)); return try_cbch(ms, s); case GSM48_MT_RR_SYSINFO_4: LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4\n"); gsm48_decode_sysinfo4(s, (struct gsm48_system_information_type_4 *) sih, msgb_l3len(msg)); return try_cbch(ms, s); default: return 0; } }
static void handle_lu_request(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct gsm48_hdr *gh; struct gsm48_loc_upd_req *lu; struct gsm48_loc_area_id lai; struct gsm_network *net; if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) { LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg)); return; } net = conn->bts->network; gh = msgb_l3(msg); lu = (struct gsm48_loc_upd_req *) gh->data; gsm48_generate_lai(&lai, net->country_code, net->network_code, conn->bts->location_area_code); if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) { LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n"); conn->sccp_con->new_subscriber = 1; } }
static void handle_ass_fail(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct bsc_api *api = conn->bts->network->bsc_api; uint8_t *rr_failure; struct gsm48_hdr *gh; if (conn->lchan != msg->lchan) { LOGP(DMSC, LOGL_ERROR, "Assignment failure should occur on primary lchan.\n"); return; } /* stop the timer and release it */ osmo_timer_del(&conn->T10); lchan_release(conn->secondary_lchan, 0, 1); conn->secondary_lchan = NULL; gh = msgb_l3(msg); if (msgb_l3len(msg) - sizeof(*gh) != 1) { LOGP(DMSC, LOGL_ERROR, "assignemnt failure unhandled: %lu\n", msgb_l3len(msg) - sizeof(*gh)); rr_failure = NULL; } else { rr_failure = &gh->data[0]; } api->assign_fail(conn, GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, rr_failure); }
/* Chapter 9.1.16 Handover complete */ static void handle_rr_ho_compl(struct msgb *msg) { struct lchan_signal_data sig; struct gsm48_hdr *gh = msgb_l3(msg); DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n", rr_cause_name(gh->data[0])); sig.lchan = msg->lchan; sig.mr = NULL; osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig); /* FIXME: release old channel */ }
/* Chapter 9.1.17 Handover Failure */ static void handle_rr_ho_fail(struct msgb *msg) { struct lchan_signal_data sig; struct gsm48_hdr *gh = msgb_l3(msg); DEBUGP(DRR, "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); sig.lchan = msg->lchan; sig.mr = NULL; osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig); /* FIXME: release allocated new channel */ }
/** * This is used to scan a message for extra functionality of the BSC. This * includes scanning for location updating requests/acceptd and then send * a welcome USSD message to the subscriber. */ int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); uint8_t pdisc = gh->proto_discr & 0x0f; uint8_t mtype = gh->msg_type & 0xbf; if (pdisc == GSM48_PDISC_MM) { if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST) handle_lu_request(conn, msg); } else if (pdisc == GSM48_PDISC_RR) { if (mtype == GSM48_MT_RR_PAG_RESP) handle_page_resp(conn, msg); } return 0; }
/* 9.1.18 IMMEDIATE ASSIGNMENT is received */ static int imm_ass(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_imm_ass *ia = msgb_l3(msg); LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT:\n"); if (state != SCAN_STATE_RACH) { LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); return 0; } /* request ref */ if (match_ra(ms, &ia->req_ref)) { return ta_result(ia->timing_advance); } LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); return 0; }
/* we will need to stop the paging request */ static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) { uint8_t mi_type; char mi_string[GSM48_MI_SIZE]; struct gsm48_hdr *gh; struct gsm48_pag_resp *resp; struct gsm_subscriber *subscr; if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) { LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg)); return -1; } gh = msgb_l3(msg); resp = (struct gsm48_pag_resp *) &gh->data[0]; gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh), mi_string, &mi_type); DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n", mi_type, mi_string); switch (mi_type) { case GSM_MI_TYPE_TMSI: subscr = subscr_active_by_tmsi(conn->bts->network, tmsi_from_string(mi_string)); break; case GSM_MI_TYPE_IMSI: subscr = subscr_active_by_imsi(conn->bts->network, mi_string); break; default: subscr = NULL; break; } if (!subscr) { LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n"); return -1; } paging_request_stop(conn->bts, subscr, conn, msg); subscr_put(subscr); return 0; }
/* receive CCCH at RR layer */ static int pch_agch(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_system_information_type_header *sih = msgb_l3(msg); switch (sih->system_information) { case GSM48_MT_RR_PAG_REQ_1: case GSM48_MT_RR_PAG_REQ_2: case GSM48_MT_RR_PAG_REQ_3: return 0; case GSM48_MT_RR_IMM_ASS: return imm_ass(ms, msg); case GSM48_MT_RR_IMM_ASS_EXT: return imm_ass_ext(ms, msg); case GSM48_MT_RR_IMM_ASS_REJ: return imm_ass_rej(ms, msg); default: return -EINVAL; } }
/* Take a B4 format message from L1 and create RSLms UNIT DATA IND */ static int send_rslms_rll_l3_ui(struct lapdm_msg_ctx *mctx, struct msgb *msg) { uint8_t l3_len = msg->tail - (uint8_t *)msgb_l3(msg); struct abis_rsl_rll_hdr *rllh; /* Add the RSL + RLL header */ msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); msgb_push(msg, 2 + 2); rsl_rll_push_hdr(msg, RSL_MT_UNIT_DATA_IND, mctx->chan_nr, mctx->link_id, 1); rllh = (struct abis_rsl_rll_hdr *)msgb_l2(msg); rllh->data[0] = RSL_IE_TIMING_ADVANCE; rllh->data[1] = mctx->ta_ind; rllh->data[2] = RSL_IE_MS_POWER; rllh->data[3] = mctx->tx_power_ind; return rslms_sendmsg(msg, mctx->dl->entity); }
/* decide if we need to reroute a message as part of a silent call */ int silent_call_reroute(struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); u_int8_t pdisc = gh->proto_discr & 0x0f; int i; /* if we're not part of a silent call, never reroute */ if (!msg->lchan->silent_call) return 0; /* check if we are a special message that is handled in openbsc */ for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) { if (silent_call_accept[i].pdisc == pdisc && silent_call_accept[i].msg_type == gh->msg_type) return 0; } /* otherwise, reroute */ return 1; }
static void handle_classmark_chg(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; struct gsm48_hdr *gh = msgb_l3(msg); unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); uint8_t cm2_len, cm3_len = 0; uint8_t *cm2, *cm3 = NULL; DEBUGP(DRR, "CLASSMARK CHANGE "); /* classmark 2 */ cm2_len = gh->data[0]; cm2 = &gh->data[1]; DEBUGPC(DRR, "CM2(len=%u) ", cm2_len); if (payload_len > cm2_len + 1) { /* we must have a classmark3 */ if (gh->data[cm2_len+1] != 0x20) { DEBUGPC(DRR, "ERR CM3 TAG\n"); return; } if (cm2_len > 3) { DEBUGPC(DRR, "CM2 too long!\n"); return; } cm3_len = gh->data[cm2_len+2]; cm3 = &gh->data[cm2_len+3]; if (cm3_len > 14) { DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len); return; } DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len); } api->classmark_chg(conn, cm2, cm2_len, cm3, cm3_len); }
static void dispatch_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) { struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; struct gsm48_hdr *gh; uint8_t pdisc; int rc; if (msgb_l3len(msg) < sizeof(*gh)) { LOGP(DMSC, LOGL_ERROR, "Message too short for a GSM48 header.\n"); return; } gh = msgb_l3(msg); pdisc = gh->proto_discr & 0x0f; /* the idea is to handle all RR messages here, and only hand * MM/CC/SMS-CP/LCS up to the MSC. Some messages like PAGING * RESPONSE or CM SERVICE REQUEST will not be covered here, as * they are only possible in the first L3 message of each L2 * channel, i.e. 'conn' will not exist and gsm0408_rcvmsg() * will call api->compl_l3() for it */ switch (pdisc) { case GSM48_PDISC_RR: switch (gh->msg_type) { case GSM48_MT_RR_GPRS_SUSP_REQ: DEBUGP(DRR, "GRPS SUSPEND REQUEST\n"); break; case GSM48_MT_RR_STATUS: LOGP(DRR, LOGL_NOTICE, "RR STATUS (cause: %s)\n", rr_cause_name(gh->data[0])); break; case GSM48_MT_RR_MEAS_REP: /* This shouldn't actually end up here, as RSL treats * L3 Info of 08.58 MEASUREMENT REPORT different by calling * directly into gsm48_parse_meas_rep */ LOGP(DMEAS, LOGL_ERROR, "DIRECT GSM48 MEASUREMENT REPORT ?!? "); break; case GSM48_MT_RR_HANDO_COMPL: handle_rr_ho_compl(msg); break; case GSM48_MT_RR_HANDO_FAIL: handle_rr_ho_fail(msg); break; case GSM48_MT_RR_CIPH_M_COMPL: if (api->cipher_mode_compl) api->cipher_mode_compl(conn, msg, conn->lchan->encr.alg_id); break; case GSM48_MT_RR_ASS_COMPL: handle_ass_compl(conn, msg); break; case GSM48_MT_RR_ASS_FAIL: handle_ass_fail(conn, msg); break; case GSM48_MT_RR_CHAN_MODE_MODIF_ACK: osmo_timer_del(&conn->T10); rc = gsm48_rx_rr_modif_ack(msg); if (rc < 0) { api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); } else if (rc >= 0) { api->assign_compl(conn, 0, lchan_to_chosen_channel(conn->lchan), conn->lchan->encr.alg_id, chan_mode_to_speech(conn->lchan)); } break; case GSM48_MT_RR_CLSM_CHG: handle_classmark_chg(conn, msg); break; case GSM48_MT_RR_APP_INFO: /* Passing RR APP INFO to MSC, not quite * according to spec */ if (api->dtap) api->dtap(conn, link_id, msg); break; default: /* Normally, a MSC should never receive RR * messages, but we'd rather forward what we * don't know than drop it... */ LOGP(DRR, LOGL_NOTICE, "BSC: Passing unknown 04.08 " "RR message type 0x%02x to MSC\n", gh->msg_type); if (api->dtap) api->dtap(conn, link_id, msg); } break; default: if (api->dtap) api->dtap(conn, link_id, msg); break; } }
static int gsm48_rx_imm_ass(struct msgb *msg, struct osmocom_ms *ms) { struct gsm48_imm_ass *ia = msgb_l3(msg); uint8_t ch_type, ch_subch, ch_ts; int rv; /* Discard packet TBF assignement */ if (ia->page_mode & 0xf0) return 0; /* FIXME: compare RA and GSM time with when we sent RACH req */ rsl_dec_chan_nr(ia->chan_desc.chan_nr, &ch_type, &ch_subch, &ch_ts); if (!ia->chan_desc.h0.h) { /* Non-hopping */ uint16_t arfcn; arfcn = ia->chan_desc.h0.arfcn_low | (ia->chan_desc.h0.arfcn_high << 8); DEBUGP(DRR, "GSM48 IMM ASS (ra=0x%02x, chan_nr=0x%02x, " "ARFCN=%u, TS=%u, SS=%u, TSC=%u) ", ia->req_ref.ra, ia->chan_desc.chan_nr, arfcn, ch_ts, ch_subch, ia->chan_desc.h0.tsc); if (ch_ts >= 4) { DEBUGPC(DRR, "UNSUPPORTED!\n"); return 0; } /* request L1 to go to dedicated mode on assigned channel */ rv = l1ctl_tx_dm_est_req_h0(ms, arfcn, ia->chan_desc.chan_nr, ia->chan_desc.h0.tsc); } else { /* Hopping */ uint8_t maio, hsn, ma_len; uint16_t ma[64], arfcn; int i, j, k; hsn = ia->chan_desc.h1.hsn; maio = ia->chan_desc.h1.maio_low | (ia->chan_desc.h1.maio_high << 2); DEBUGP(DRR, "GSM48 IMM ASS (ra=0x%02x, chan_nr=0x%02x, " "HSN=%u, MAIO=%u, TS=%u, SS=%u, TSC=%u) ", ia->req_ref.ra, ia->chan_desc.chan_nr, hsn, maio, ch_ts, ch_subch, ia->chan_desc.h1.tsc); /* decode mobile allocation */ ma_len = 0; for (i=1, j=0; i<=1024; i++) { arfcn = i & 1023; if (app_state.cell_arfcns[arfcn].mask & 0x01) { k = ia->mob_alloc_len - (j>>3) - 1; if (ia->mob_alloc[k] & (1 << (j&7))) { ma[ma_len++] = arfcn; } j++; } } if (ch_ts >= 4) { DEBUGPC(DRR, "UNSUPPORTED!\n"); return 0; } /* request L1 to go to dedicated mode on assigned channel */ rv = l1ctl_tx_dm_est_req_h1(ms, maio, hsn, ma, ma_len, ia->chan_desc.chan_nr, ia->chan_desc.h1.tsc); }
/** * This method used to send a l1ctl_tx_dm_est_req_h0 or * a l1ctl_tx_dm_est_req_h1 to the layer1 to follow this * assignment. The code has been removed. */ static int gsm48_rx_imm_ass(struct msgb *msg, struct osmocom_ms *ms) { struct gsm48_imm_ass *ia = msgb_l3(msg); uint8_t ch_type, ch_subch, ch_ts; int rv; /* Discard packet TBF assignement */ if (ia->page_mode & 0xf0) return 0; /* If we're not ready yet, or just busy ... */ if ((!app_state.has_si1) || (app_state.dch_state != DCH_NONE)) return 0; rsl_dec_chan_nr(ia->chan_desc.chan_nr, &ch_type, &ch_subch, &ch_ts); if (!ia->chan_desc.h0.h) { /* Non-hopping */ uint16_t arfcn; arfcn = ia->chan_desc.h0.arfcn_low | (ia->chan_desc.h0.arfcn_high << 8); LOGP(DRR, LOGL_NOTICE, "GSM48 IMM ASS (ra=0x%02x, chan_nr=0x%02x, " "ARFCN=%u, TS=%u, SS=%u, TSC=%u) ", ia->req_ref.ra, ia->chan_desc.chan_nr, arfcn, ch_ts, ch_subch, ia->chan_desc.h0.tsc); /* request L1 to go to dedicated mode on assigned channel */ rv = l1ctl_tx_dm_est_req_h0(ms, arfcn, ia->chan_desc.chan_nr, ia->chan_desc.h0.tsc, GSM48_CMODE_SIGN, 0); } else { /* Hopping */ uint8_t maio, hsn, ma_len; uint16_t ma[64], arfcn; int i, j, k; hsn = ia->chan_desc.h1.hsn; maio = ia->chan_desc.h1.maio_low | (ia->chan_desc.h1.maio_high << 2); LOGP(DRR, LOGL_NOTICE, "GSM48 IMM ASS (ra=0x%02x, chan_nr=0x%02x, " "HSN=%u, MAIO=%u, TS=%u, SS=%u, TSC=%u) ", ia->req_ref.ra, ia->chan_desc.chan_nr, hsn, maio, ch_ts, ch_subch, ia->chan_desc.h1.tsc); /* decode mobile allocation */ ma_len = 0; for (i=1, j=0; i<=1024; i++) { arfcn = i & 1023; if (app_state.cell_arfcns[arfcn].mask & 0x01) { k = ia->mob_alloc_len - (j>>3) - 1; if (ia->mob_alloc[k] & (1 << (j&7))) { ma[ma_len++] = arfcn; } j++; } } /* request L1 to go to dedicated mode on assigned channel */ rv = l1ctl_tx_dm_est_req_h1(ms, maio, hsn, ma, ma_len, ia->chan_desc.chan_nr, ia->chan_desc.h1.tsc, GSM48_CMODE_SIGN, 0); }
/* receive BCCH at RR layer */ static int bcch(struct osmocom_ms *ms, struct msgb *msg) { struct gsm48_sysinfo *s = &sysinfo; struct gsm48_system_information_type_header *sih = msgb_l3(msg); uint8_t ccch_mode; if (msgb_l3len(msg) != 23) { LOGP(DRR, LOGL_NOTICE, "Invalid BCCH message length\n"); return -EINVAL; } switch (sih->system_information) { case GSM48_MT_RR_SYSINFO_1: if (!memcmp(sih, s->si1_msg, sizeof(s->si1_msg))) return 0; LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n"); gsm48_decode_sysinfo1(s, (struct gsm48_system_information_type_1 *) sih, msgb_l3len(msg)); return new_sysinfo(); case GSM48_MT_RR_SYSINFO_2: if (!memcmp(sih, s->si2_msg, sizeof(s->si2_msg))) return 0; LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2\n"); gsm48_decode_sysinfo2(s, (struct gsm48_system_information_type_2 *) sih, msgb_l3len(msg)); return new_sysinfo(); case GSM48_MT_RR_SYSINFO_2bis: if (!memcmp(sih, s->si2b_msg, sizeof(s->si2b_msg))) return 0; LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2bis\n"); gsm48_decode_sysinfo2bis(s, (struct gsm48_system_information_type_2bis *) sih, msgb_l3len(msg)); return new_sysinfo(); case GSM48_MT_RR_SYSINFO_2ter: if (!memcmp(sih, s->si2t_msg, sizeof(s->si2t_msg))) return 0; LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2ter\n"); gsm48_decode_sysinfo2ter(s, (struct gsm48_system_information_type_2ter *) sih, msgb_l3len(msg)); return new_sysinfo(); case GSM48_MT_RR_SYSINFO_3: if (!memcmp(sih, s->si3_msg, sizeof(s->si3_msg))) return 0; LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 3\n"); gsm48_decode_sysinfo3(s, (struct gsm48_system_information_type_3 *) sih, msgb_l3len(msg)); ccch_mode = (s->ccch_conf == 1) ? CCCH_MODE_COMBINED : CCCH_MODE_NON_COMBINED; LOGP(DRR, LOGL_INFO, "Changing CCCH_MODE to %d\n", ccch_mode); l1ctl_tx_ccch_mode_req(ms, ccch_mode); return new_sysinfo(); case GSM48_MT_RR_SYSINFO_4: if (!memcmp(sih, s->si4_msg, sizeof(s->si4_msg))) return 0; LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4\n"); gsm48_decode_sysinfo4(s, (struct gsm48_system_information_type_4 *) sih, msgb_l3len(msg)); return new_sysinfo(); default: return -EINVAL; } }