diff options
Diffstat (limited to 'src/host/layer23/src/mobile/gsm48_rr.c')
-rw-r--r-- | src/host/layer23/src/mobile/gsm48_rr.c | 4545 |
1 files changed, 4545 insertions, 0 deletions
diff --git a/src/host/layer23/src/mobile/gsm48_rr.c b/src/host/layer23/src/mobile/gsm48_rr.c new file mode 100644 index 00000000..37d8a671 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm48_rr.c @@ -0,0 +1,4545 @@ +#warning rr on MDL error handling (as specified in 04.08 / 04.06) +/* + * (C) 2010 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. + * + */ + +/* Very short description of some of the procedures: + * + * A radio ressource request causes sendig a channel request on RACH. + * After receiving of an immediate assignment the link will be establised. + * After the link is established, the dedicated mode is entered and confirmed. + * + * A Paging request also triggers the channel request as above... + * After the link is established, the dedicated mode is entered and indicated. + * + * During dedicated mode, messages are transferred. + * + * When an assignment command or a handover command is received, the current + * link is released. After release, the new channel is activated and the + * link is established again. After link is establised, pending messages from + * radio ressource are sent. + * + * When the assignment or handover fails, the old channel is activate and the + * link is established again. Also pending messages are sent. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <arpa/inet.h> + +#include <osmocore/msgb.h> +#include <osmocore/utils.h> +#include <osmocore/rsl.h> +#include <osmocore/gsm48.h> +#include <osmocore/bitvec.h> + +#include <osmocom/osmocom_data.h> +#include <osmocom/l1l2_interface.h> +#include <osmocom/logging.h> +#include <osmocom/networks.h> +#include <osmocom/l1ctl.h> +#include <osmocom/vty.h> + +static void start_rr_t_monitor(struct gsm48_rrlayer *rr, int sec, int micro); +static void stop_rr_t_monitor(struct gsm48_rrlayer *rr); +static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_rr_dl_est(struct osmocom_ms *ms); + +/* + * support + */ + +#define MIN(a, b) ((a < b) ? a : b) + +int gsm48_decode_lai(struct gsm48_loc_area_id *lai, uint16_t *mcc, + uint16_t *mnc, uint16_t *lac) +{ + *mcc = ((lai->digits[0] & 0x0f) << 8) + | (lai->digits[0] & 0xf0) + | (lai->digits[1] & 0x0f); + *mnc = ((lai->digits[2] & 0x0f) << 8) + | (lai->digits[2] & 0xf0) + | ((lai->digits[1] & 0xf0) >> 4); + *lac = ntohs(lai->lac); + + return 0; +} + +static int gsm48_encode_chan_h0(struct gsm48_chan_desc *cd, uint8_t tsc, + uint16_t arfcn) +{ + cd->h0.tsc = tsc; + cd->h0.h = 0; + cd->h0.arfcn_low = arfcn & 0xff; + cd->h0.arfcn_high = arfcn >> 8; + + return 0; +} + +static int gsm48_encode_chan_h1(struct gsm48_chan_desc *cd, uint8_t tsc, + uint8_t maio, uint8_t hsn) +{ + cd->h1.tsc = tsc; + cd->h1.h = 1; + cd->h1.maio_low = maio & 0x03; + cd->h1.maio_high = maio >> 2; + cd->h1.hsn = hsn; + + return 0; +} + + +static int gsm48_decode_chan_h0(struct gsm48_chan_desc *cd, uint8_t *tsc, + uint16_t *arfcn) +{ + *tsc = cd->h0.tsc; + *arfcn = cd->h0.arfcn_low | (cd->h0.arfcn_high << 8); + + return 0; +} + +static int gsm48_decode_chan_h1(struct gsm48_chan_desc *cd, uint8_t *tsc, + uint8_t *maio, uint8_t *hsn) +{ + *tsc = cd->h1.tsc; + *maio = cd->h1.maio_low | (cd->h1.maio_high << 2); + *hsn = cd->h1.hsn; + + return 0; +} + +/* 10.5.2.38 decode Starting time IE */ +static int gsm48_decode_start_time(struct gsm48_rr_cd *cd, + struct gsm48_start_time *st) +{ + cd->start_t1 = st->t1; + cd->start_t2 = st->t2; + cd->start_t3 = (st->t3_high << 3) | st->t3_low; + + return 0; +} + +/* decode "BA Range" (10.5.2.1a) */ +static int gsm48_decode_ba_range(const uint8_t *ba, uint8_t ba_len, + uint32_t *range, uint8_t *ranges, int max_ranges) +{ + /* ba = pointer to IE without IE type and length octets + * ba_len = number of octets + * range = pointer to store decoded range + * ranges = number of ranges decoded + * max_ranges = maximum number of decoded ranges that can be stored + */ + uint16_t lower, higher; + int i, n, required_octets; + + /* find out how much ba ranges will be decoded */ + n = *ba++; + ba_len --; + required_octets = 5 * (n >> 1) + 3 * (n & 1); + if (required_octets > ba_len) { + LOGP(DRR, LOGL_NOTICE, "BA range IE too short: %d ranges " + "require %d octets, but only %d octets remain.\n", + n, required_octets, ba_len); + *ranges = 0; + return -EINVAL; + } + if (max_ranges > n) + LOGP(DRR, LOGL_NOTICE, "BA range %d exceed the maximum number " + "of ranges supported by this mobile (%d).\n", + n, max_ranges); + n = max_ranges; + + /* decode ranges */ + for (i = 0; i < n; i++) { + if (!(i & 1)) { + /* decode even range number */ + lower = *ba++ << 2; + lower |= (*ba >> 6); + higher = (*ba++ & 0x3f) << 4; + higher |= *ba >> 4; + } else { + lower = (*ba++ & 0x0f) << 6; + lower |= *ba >> 2; + higher = (*ba++ & 0x03) << 8; + higher |= *ba++; + /* decode odd range number */ + } + *range++ = (higher << 16) | lower; + } + *ranges = n; + + return 0; +} + +/* + * state transition + */ + +const char *gsm48_rr_state_names[] = { + "idle", + "connection pending", + "dedicated", + "release pending", +}; + +static void new_rr_state(struct gsm48_rrlayer *rr, int state) +{ + if (state < 0 || state >= + (sizeof(gsm48_rr_state_names) / sizeof(char *))) + return; + + /* must check against equal state */ + if (rr->state == state) { + LOGP(DRR, LOGL_INFO, "equal state ? %s\n", + gsm48_rr_state_names[rr->state]); + return; + } + + LOGP(DRR, LOGL_INFO, "new state %s -> %s\n", + gsm48_rr_state_names[rr->state], gsm48_rr_state_names[state]); + + rr->state = state; + + if (state == GSM48_RR_ST_IDLE) { + struct msgb *msg, *nmsg; + struct gsm322_msg *em; + + /* release dedicated mode, if any */ + tx_ph_dm_rel_req(rr->ms); + l1ctl_tx_reset_req(rr->ms, L1CTL_RES_T_FULL); + /* free establish message, if any */ + rr->rr_est_req = 0; + if (rr->rr_est_msg) { + msgb_free(rr->rr_est_msg); + rr->rr_est_msg = NULL; + } + /* free all pending messages */ + while((msg = msgb_dequeue(&rr->downqueue))) + msgb_free(msg); + /* clear all descriptions of last channel */ + memset(&rr->cd_now, 0, sizeof(rr->cd_now)); + /* reset cipering */ + rr->cipher_on = 0; + /* tell cell selection process to return to idle mode + * NOTE: this must be sent unbuffered, because it will + * leave camping state, so it locks against subsequent + * establishment of dedicated channel, before the + * cell selection process returned to camping state + * again. (after cell reselection) + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_RET_IDLE); + if (!nmsg) + return; + /* return to same cell after LOC.UPD. */ + if (rr->est_cause == RR_EST_CAUSE_LOC_UPD) { + em = (struct gsm322_msg *) nmsg->data; + em->same_cell = 1; + } + gsm322_c_event(rr->ms, nmsg); + msgb_free(nmsg); + /* reset any BA range */ + rr->ba_ranges = 0; + } +} + +/* + * messages + */ + +/* names of RR-SAP */ +static const struct value_string gsm48_rr_msg_names[] = { + { GSM48_RR_EST_REQ, "RR_EST_REQ" }, + { GSM48_RR_EST_IND, "RR_EST_IND" }, + { GSM48_RR_EST_CNF, "RR_EST_CNF" }, + { GSM48_RR_REL_IND, "RR_REL_IND" }, + { GSM48_RR_SYNC_IND, "RR_SYNC_IND" }, + { GSM48_RR_DATA_REQ, "RR_DATA_REQ" }, + { GSM48_RR_DATA_IND, "RR_DATA_IND" }, + { GSM48_RR_UNIT_DATA_IND, "RR_UNIT_DATA_IND" }, + { GSM48_RR_ABORT_REQ, "RR_ABORT_REQ" }, + { GSM48_RR_ABORT_IND, "RR_ABORT_IND" }, + { GSM48_RR_ACT_REQ, "RR_ACT_REQ" }, + { 0, NULL } +}; + +const char *get_rr_name(int value) +{ + return get_value_string(gsm48_rr_msg_names, value); +} + +/* allocate GSM 04.08 layer 3 message */ +struct msgb *gsm48_l3_msgb_alloc(void) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(L3_ALLOC_SIZE+L3_ALLOC_HEADROOM, + L3_ALLOC_HEADROOM, "GSM 04.08 L3"); + if (!msg) + return NULL; + msg->l3h = msg->data; + + return msg; +} + +/* allocate GSM 04.06 layer 2 RSL message */ +struct msgb *gsm48_rsl_msgb_alloc(void) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(RSL_ALLOC_SIZE+RSL_ALLOC_HEADROOM, + RSL_ALLOC_HEADROOM, "GSM 04.06 RSL"); + if (!msg) + return NULL; + msg->l2h = msg->data; + + return msg; +} + +/* allocate GSM 04.08 message (RR-SAP) */ +struct msgb *gsm48_rr_msgb_alloc(int msg_type) +{ + struct msgb *msg; + struct gsm48_rr_hdr *rrh; + + msg = msgb_alloc_headroom(RR_ALLOC_SIZE+RR_ALLOC_HEADROOM, + RR_ALLOC_HEADROOM, "GSM 04.08 RR"); + if (!msg) + return NULL; + + rrh = (struct gsm48_rr_hdr *) msgb_put(msg, sizeof(*rrh)); + rrh->msg_type = msg_type; + + return msg; +} + +/* queue message (RR-SAP) */ +int gsm48_rr_upmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + msgb_enqueue(&mm->rr_upqueue, msg); + + return 0; +} + +/* push rsl header and send (RSL-SAP) */ +static int gsm48_send_rsl(struct osmocom_ms *ms, uint8_t msg_type, + struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (!msg->l3h) { + printf("FIX l3h\n"); + exit (0); + } + rsl_rll_push_l3(msg, msg_type, rr->cd_now.chan_nr, + rr->cd_now.link_id, 1); + + return rslms_recvmsg(msg, ms); +} + +/* push rsl header + release mode and send (RSL-SAP) */ +static int gsm48_send_rsl_rel(struct osmocom_ms *ms, uint8_t msg_type, + struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + rsl_rll_push_hdr(msg, msg_type, rr->cd_now.chan_nr, + rr->cd_now.link_id, 1); + + return rslms_recvmsg(msg, ms); +} + +/* enqueue messages (RSL-SAP) */ +static int gsm48_rx_rsl(struct msgb *msg, struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + msgb_enqueue(&rr->rsl_upqueue, msg); + + return 0; +} + +/* dequeue messages (RSL-SAP) */ +int gsm48_rsl_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&rr->rsl_upqueue))) { + /* msg is freed there */ + gsm48_rcv_rsl(ms, msg); + work = 1; /* work done */ + } + + return work; +} + +int gsm48_rr_start_monitor(struct osmocom_ms *ms) +{ + ms->rrlayer.monitor = 1; + memset(&ms->meas, 0, sizeof(&ms->meas)); + start_rr_t_monitor(&ms->rrlayer, 1, 0); + + return 0; +} + +int gsm48_rr_stop_monitor(struct osmocom_ms *ms) +{ + ms->rrlayer.monitor = 0; + memset(&ms->meas, 0, sizeof(&ms->meas)); + stop_rr_t_monitor(&ms->rrlayer); + + return 0; +} + +/* + * timers handling + */ + +/* special timer to monitor measurements */ +static void timeout_rr_monitor(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct gsm322_cellsel *cs = &rr->ms->cellsel; + struct rx_meas_stat *meas = &rr->ms->meas; + struct gsm_settings *set = &rr->ms->settings; + int rxlev, berr; + uint8_t ch_type, ch_subch, ch_ts; + char text[256]; + + if (!cs->selected) { + sprintf(text, "MON: no cell selected"); + } else if (!meas->frames) { + sprintf(text, "MON: no cell info"); + } else { + rxlev = meas->rxlev / meas->frames; + berr = meas->berr / meas->frames; + sprintf(text, "MON: arfcn=%d lev=%s ber=%2d LAI=%s %s %04x " + "ID=%04x", cs->sel_arfcn, gsm_print_rxlev(rxlev), + berr, gsm_print_mcc(cs->sel_mcc), + gsm_print_mnc(cs->sel_mnc), cs->sel_lac, cs->sel_id); + if (rr->state == GSM48_RR_ST_DEDICATED) { + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, + &ch_subch, &ch_ts); + sprintf(text + strlen(text), " TA=%d pwr=%d TS=%d", + rr->ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : rr->ind_tx_power, ch_ts); + if (ch_type == RSL_CHAN_SDCCH8_ACCH + || ch_type == RSL_CHAN_SDCCH4_ACCH) + sprintf(text + strlen(text), "/%d", ch_subch); + } + } + vty_notify(rr->ms, "%s\n", text); + + memset(meas, 0, sizeof(*meas)); + start_rr_t_monitor(rr, 1, 0); +} + +/* special timer to ensure that UA is sent before disconnecting channel */ +static void timeout_rr_t_rel_wait(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + + LOGP(DRR, LOGL_INFO, "L2 release timer has fired, done waiting\n"); + + /* return to idle now */ + new_rr_state(rr, GSM48_RR_ST_IDLE); +} + +/* 3.4.13.1.1: Timeout of T3110 */ +static void timeout_rr_t3110(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct osmocom_ms *ms = rr->ms; + struct msgb *nmsg; + uint8_t *mode; + + LOGP(DRR, LOGL_INFO, "timer T3110 has fired, release locally\n"); + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* disconnect the main signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 1; /* local release */ + gsm48_send_rsl_rel(ms, RSL_MT_REL_REQ, nmsg); + + return; +} + +static void timeout_rr_t3122(void *arg) +{ + LOGP(DRR, LOGL_INFO, "timer T3122 has fired\n"); +} + +static void timeout_rr_t3126(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct osmocom_ms *ms = rr->ms; + + LOGP(DRR, LOGL_INFO, "timer T3126 has fired\n"); + if (rr->rr_est_req) { + struct msgb *msg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + struct gsm48_rr_hdr *rrh; + + LOGP(DSUM, LOGL_INFO, "Requesting channel failed\n"); + if (!msg) + return; + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->cause = RR_REL_CAUSE_RA_FAILURE; + gsm48_rr_upmsg(ms, msg); + } + + new_rr_state(rr, GSM48_RR_ST_IDLE); +} + +static void start_rr_t_monitor(struct gsm48_rrlayer *rr, int sec, int micro) +{ + rr->t_monitor.cb = timeout_rr_monitor; + rr->t_monitor.data = rr; + bsc_schedule_timer(&rr->t_monitor, sec, micro); +} + +static void start_rr_t_rel_wait(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T_rel_wait with %d seconds\n", sec); + rr->t_rel_wait.cb = timeout_rr_t_rel_wait; + rr->t_rel_wait.data = rr; + bsc_schedule_timer(&rr->t_rel_wait, sec, micro); +} + +static void start_rr_t3110(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3110 with %d seconds\n", sec); + rr->t3110.cb = timeout_rr_t3110; + rr->t3110.data = rr; + bsc_schedule_timer(&rr->t3110, sec, micro); +} + +static void start_rr_t3122(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3122 with %d seconds\n", sec); + rr->t3122.cb = timeout_rr_t3122; + rr->t3122.data = rr; + bsc_schedule_timer(&rr->t3122, sec, micro); +} + +static void start_rr_t3126(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3126 with %d seconds\n", sec); + rr->t3126.cb = timeout_rr_t3126; + rr->t3126.data = rr; + bsc_schedule_timer(&rr->t3126, sec, micro); +} + +static void stop_rr_t_monitor(struct gsm48_rrlayer *rr) +{ + if (bsc_timer_pending(&rr->t_monitor)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T_monitor\n"); + bsc_del_timer(&rr->t_monitor); + } +} + +static void stop_rr_t_rel_wait(struct gsm48_rrlayer *rr) +{ + if (bsc_timer_pending(&rr->t_rel_wait)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T_rel_wait\n"); + bsc_del_timer(&rr->t_rel_wait); + } +} + +static void stop_rr_t3110(struct gsm48_rrlayer *rr) +{ + if (bsc_timer_pending(&rr->t3110)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3110\n"); + bsc_del_timer(&rr->t3110); + } +} + +static void stop_rr_t3122(struct gsm48_rrlayer *rr) +{ + if (bsc_timer_pending(&rr->t3122)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3122\n"); + bsc_del_timer(&rr->t3122); + } +} + +static void stop_rr_t3126(struct gsm48_rrlayer *rr) +{ + if (bsc_timer_pending(&rr->t3126)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3126\n"); + bsc_del_timer(&rr->t3126); + } +} + +/* + * status + */ + +/* send rr status request */ +static int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_rr_status *st; + + LOGP(DRR, LOGL_INFO, "RR STATUS (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + st = (struct gsm48_rr_status *) msgb_put(nmsg, sizeof(*st)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL; + + /* rr cause */ + st->rr_cause = cause; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg); +} + +/* + * ciphering + */ + +/* send chiperhing mode complete */ +static int gsm48_rr_tx_cip_mode_cpl(struct osmocom_ms *ms, uint8_t cr) +{ + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + struct gsm48_hdr *gh; + uint8_t buf[11], *tlv; + + LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMPLETE (cr %d)\n", cr); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL; + + /* MI */ + if (cr) { + gsm48_generate_mid_from_imsi(buf, set->imeisv); + tlv = msgb_put(nmsg, 2 + buf[1]); + memcpy(tlv, buf, 2 + buf[1]); + } + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg); +} + +/* receive ciphering mode command */ +static int gsm48_rr_rx_cip_mode_cmd(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_support *sup = &ms->support; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_cip_mode_cmd *cm = (struct gsm48_cip_mode_cmd *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cm); + uint8_t sc, alg_id, cr; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CIPHERING MODE COMMAND " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* cipher mode setting */ + sc = cm->sc; + alg_id = cm->alg_id; + /* cipher mode response */ + cr = cm->cr; + + if (sc) + LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMMAND (sc=%u, cr=%u)", + sc, cr); + else + LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMMAND (sc=%u, " + "algo=A5/%d cr=%u)", sc, alg_id + 1, cr); + + /* 3.4.7.2 */ + if (rr->cipher_on && sc) { + LOGP(DRR, LOGL_INFO, "cipering already applied.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* check if we actually support this cipher */ + if ((alg_id == GSM_CIPHER_A5_1 && !sup->a5_1) + || (alg_id == GSM_CIPHER_A5_2 && !sup->a5_2) + || (alg_id == GSM_CIPHER_A5_3 && !sup->a5_3) + || (alg_id == GSM_CIPHER_A5_4 && !sup->a5_4) + || (alg_id == GSM_CIPHER_A5_5 && !sup->a5_5) + || (alg_id == GSM_CIPHER_A5_6 && !sup->a5_6) + || (alg_id == GSM_CIPHER_A5_7 && !sup->a5_7)) + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_CHAN_MODE_UNACCT); + + /* change to ciphering */ +#ifdef TODO + rsl command to activate ciperhing +#endif + rr->cipher_on = sc, rr->cipher_type = alg_id; + + /* response */ + return gsm48_rr_tx_cip_mode_cpl(ms, cr); +} + +/* + * classmark + */ + +/* Encode "Classmark 3" (10.5.1.7) */ +static int gsm48_rr_enc_cm3(struct osmocom_ms *ms, uint8_t *buf, uint8_t *len) +{ + struct gsm_support *sup = &ms->support; + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = buf; + bv.data_len = 12; + + /* spare bit */ + bitvec_set_bit(&bv, 0); + /* band 3 supported */ + if (sup->dcs_1800) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* band 2 supported */ + if (sup->e_gsm || sup->r_gsm) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* band 1 supported */ + if (sup->p_gsm && !(sup->e_gsm || sup->r_gsm)) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* a5 bits */ + if (sup->a5_7) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + if (sup->a5_6) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + if (sup->a5_5) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + if (sup->a5_4) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* radio capability */ + if (sup->dcs_1800 && !sup->p_gsm && !(sup->e_gsm || sup->r_gsm)) { + /* dcs only */ + bitvec_set_uint(&bv, 0, 4); + bitvec_set_uint(&bv, sup->dcs_capa, 4); + } else + if (sup->dcs_1800 && (sup->p_gsm || (sup->e_gsm || sup->r_gsm))) { + /* dcs */ + bitvec_set_uint(&bv, sup->dcs_capa, 4); + /* low band */ + bitvec_set_uint(&bv, sup->low_capa, 4); + } else { + /* low band only */ + bitvec_set_uint(&bv, 0, 4); + bitvec_set_uint(&bv, sup->low_capa, 4); + } + /* r support */ + if (sup->r_gsm) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, sup->r_capa, 3); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* multi slot support */ + if (sup->ms_sup) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, sup->ms_sup, 5); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* ucs2 treatment */ + if (sup->ucs2_treat) { + bitvec_set_bit(&bv, ONE); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* support extended measurements */ + if (sup->ext_meas) { + bitvec_set_bit(&bv, ONE); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* support measurement capability */ + if (sup->meas_cap) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, sup->sms_val, 4); + bitvec_set_uint(&bv, sup->sm_val, 4); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* positioning method capability */ + if (sup->loc_serv) { + bitvec_set_bit(&bv, ONE); + bitvec_set_bit(&bv, sup->e_otd_ass == 1); + bitvec_set_bit(&bv, sup->e_otd_based == 1); + bitvec_set_bit(&bv, sup->gps_ass == 1); + bitvec_set_bit(&bv, sup->gps_based == 1); + bitvec_set_bit(&bv, sup->gps_conv == 1); + } else { + bitvec_set_bit(&bv, ZERO); + } + + /* partitial bytes will be completed */ + *len = (bv.cur_bit + 7) >> 3; + bitvec_spare_padding(&bv, (*len * 8) - 1); + + return 0; +} + +/* encode classmark 2 */ +int gsm48_rr_enc_cm2(struct osmocom_ms *ms, struct gsm48_classmark2 *cm) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_support *sup = &ms->support; + + if (rr->cd_now.arfcn >= 512 && rr->cd_now.arfcn <= 885) + cm->pwr_lev = sup->pwr_lev_1800; + else + cm->pwr_lev = sup->pwr_lev_900; + cm->a5_1 = sup->a5_1; + cm->es_ind = sup->es_ind; + cm->rev_lev = sup->rev_lev; + cm->fc = (sup->r_gsm || sup->e_gsm); + cm->vgcs = sup->vgcs; + cm->vbs = sup->vbs; + cm->sm_cap = sup->sms_ptp; + cm->ss_scr = sup->ss_ind; + cm->ps_cap = sup->ps_cap; + cm->a5_2 = sup->a5_2; + cm->a5_3 = sup->a5_3; + cm->cmsp = sup->cmsp; + cm->solsa = sup->solsa; + cm->lcsva_cap = sup->lcsva; + + return 0; +} + +/* send classmark change */ +static int gsm48_rr_tx_cm_change(struct osmocom_ms *ms) +{ + struct gsm_support *sup = &ms->support; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_cm_change *cc; + uint8_t cm3[14], *tlv; + + LOGP(DRR, LOGL_INFO, "CLASSMARK CHANGE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + cc = (struct gsm48_cm_change *) msgb_put(nmsg, sizeof(*cc)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CLSM_CHG; + + /* classmark 2 */ + cc->cm2_len = sizeof(cc->cm2); + gsm48_rr_enc_cm2(ms, &cc->cm2); + + /* classmark 3 */ + if (sup->dcs_1800 || sup->e_gsm || sup->r_gsm + || sup->a5_7 || sup->a5_6 || sup->a5_5 || sup->a5_4 + || sup->ms_sup + || sup->ucs2_treat + || sup->ext_meas || sup->meas_cap + || sup->loc_serv) { + cc->cm2.cm3 = 1; + cm3[0] = GSM48_IE_CLASSMARK3; + gsm48_rr_enc_cm3(ms, cm3 + 2, &cm3[1]); + tlv = msgb_put(nmsg, 2 + cm3[1]); + memcpy(tlv, cm3, 2 + cm3[1]); + } + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg); +} + +/* receiving classmark enquiry */ +static int gsm48_rr_rx_cm_enq(struct osmocom_ms *ms, struct msgb *msg) +{ + /* send classmark */ + return gsm48_rr_tx_cm_change(ms); +} + +/* + * random access + */ + +/* start random access */ +static int gsm48_rr_chan_req(struct osmocom_ms *ms, int cause, int paging) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint8_t chan_req_val, chan_req_mask; + int rc; + + LOGP(DSUM, LOGL_INFO, "Establish radio link due to %s request\n", + (paging) ? "paging" : "mobility management"); + + /* ignore paging, if not camping */ + if (paging + && (!cs->selected || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL))) { + LOGP(DRR, LOGL_INFO, "Paging, but not camping, ignore.\n"); + return -EINVAL; + } + + /* tell cell selection process to leave idle mode + * NOTE: this must be sent unbuffered, because the state may not + * change until idle mode is left + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_LEAVE_IDLE); + if (!nmsg) + return -ENOMEM; + rc = gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + if (rc) { + if (paging) + return rc; + LOGP(DRR, LOGL_INFO, "Failed to leave IDLE mode.\n"); + goto undefined; + } + + /* 3.3.1.1.2 */ + new_rr_state(rr, GSM48_RR_ST_CONN_PEND); + + /* set assignment state */ + rr->wait_assign = 0; + + /* number of retransmissions (with first transmission) */ + rr->n_chan_req = s->max_retrans + 1; + +#warning HACK: always request SDCCH for test +cause = RR_EST_CAUSE_LOC_UPD; + /* generate CHAN REQ (9.1.8) */ + switch (cause) { + case RR_EST_CAUSE_EMERGENCY: + /* 101xxxxx */ + chan_req_mask = 0x1f; + chan_req_val = 0xa0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Emergency call)\n", + chan_req_val); + break; + case RR_EST_CAUSE_REESTAB_TCH_F: + chan_req_mask = 0x1f; + chan_req_val = 0xc0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (re-establish " + "TCH/F)\n", chan_req_val); + break; + case RR_EST_CAUSE_REESTAB_TCH_H: + if (s->neci) { + chan_req_mask = 0x03; + chan_req_val = 0x68; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H with NECI)\n", + chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xc0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H no NECI)\n", chan_req_val); + } + break; + case RR_EST_CAUSE_REESTAB_2_TCH_H: + if (s->neci) { + chan_req_mask = 0x03; + chan_req_val = 0x6c; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H+TCH/H with NECI)\n", + chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xc0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H+TCH/H no NECI)\n", + chan_req_val); + } + break; + case RR_EST_CAUSE_ANS_PAG_ANY: + chan_req_mask = 0x1f; + chan_req_val = 0x80; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING " + "Any channel)\n", chan_req_val); + break; + case RR_EST_CAUSE_ANS_PAG_SDCCH: + chan_req_mask = 0x0f; + chan_req_val = 0x10; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING SDCCH)\n", + chan_req_val); + break; + case RR_EST_CAUSE_ANS_PAG_TCH_F: + /* ms supports no dual rate */ + chan_req_mask = 0x1f; + chan_req_val = 0x80; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING TCH/F)\n", + chan_req_val); + break; + case RR_EST_CAUSE_ANS_PAG_TCH_ANY: + /* ms supports no dual rate */ + chan_req_mask = 0x1f; + chan_req_val = 0x80; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING TCH/H or " + "TCH/F)\n", chan_req_val); + break; + case RR_EST_CAUSE_ORIG_TCHF: + /* ms supports no dual rate */ + chan_req_mask = 0x1f; + chan_req_val = 0xe0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Orig TCH/F)\n", + chan_req_val); + break; + case RR_EST_CAUSE_LOC_UPD: + if (s->neci) { + chan_req_mask = 0x0f; + chan_req_val = 0x00; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Location " + "Update with NECI)\n", chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0x00; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Location " + "Update no NECI)\n", chan_req_val); + } + break; + case RR_EST_CAUSE_OTHER_SDCCH: + if (s->neci) { + chan_req_mask = 0x0f; + chan_req_val = 0x01; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OHTER " + "with NECI)\n", chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xe0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OTHER " + "no NECI)\n", chan_req_val); + } + break; + default: + if (!rr->rr_est_req) /* no request from MM */ + return -EINVAL; + + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: with unknown " + "establishment cause: %d\n", cause); + undefined: + LOGP(DSUM, LOGL_INFO, "Requesting channel failed\n"); + + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_UNDEFINED; + gsm48_rr_upmsg(ms, nmsg); + new_rr_state(rr, GSM48_RR_ST_IDLE); + return -EINVAL; + } + + /* store value, mask and history */ + rr->chan_req_val = chan_req_val; + rr->chan_req_mask = chan_req_mask; + rr->cr_hist[2].valid = 0; + rr->cr_hist[1].valid = 0; + rr->cr_hist[0].valid = 0; + + /* store establishment cause, so 'choose cell' selects the last cell + * after location updating */ + rr->est_cause = cause; + + /* if channel is already active somehow */ + if (cs->ccch_state == GSM322_CCCH_ST_DATA) + return gsm48_rr_tx_rand_acc(ms, NULL); + + return 0; +} + +/* send first/next channel request in conn pend state */ +int gsm48_rr_tx_rand_acc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + struct abis_rsl_cchan_hdr *ncch; + int slots; + uint8_t chan_req; + uint8_t tx_power; + + /* already assigned */ + if (rr->wait_assign == 2) + return 0; + + /* store frame number */ + if (msg) { + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + struct gsm48_req_ref *ref = + (struct gsm48_req_ref *) (ch->data + 1); + + if (msgb_l2len(msg) < sizeof(*ch) + sizeof(*ref)) { + LOGP(DRR, LOGL_ERROR, "CHAN_CNF too slort\n"); + return -EINVAL; + } + + /* shift history and store */ + memcpy(&(rr->cr_hist[2]), &(rr->cr_hist[1]), + sizeof(struct gsm48_cr_hist)); + memcpy(&(rr->cr_hist[1]), &(rr->cr_hist[0]), + sizeof(struct gsm48_cr_hist)); + rr->cr_hist[0].valid = 1; + rr->cr_hist[0].ref.ra = rr->cr_ra; + rr->cr_hist[0].ref.t1 = ref->t1; + rr->cr_hist[0].ref.t2 = ref->t2; + rr->cr_hist[0].ref.t3_low = ref->t3_low; + rr->cr_hist[0].ref.t3_high = ref->t3_high; + } + + if (cs->ccch_state != GSM322_CCCH_ST_DATA) { + LOGP(DRR, LOGL_INFO, "CCCH channel activation failed.\n"); + + if (rr->rr_est_req) { + struct msgb *msg = + gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + struct gsm48_rr_hdr *rrh; + + LOGP(DSUM, LOGL_INFO, "Requesting channel failed\n"); + if (!msg) + return -ENOMEM; + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->cause = RR_REL_CAUSE_RA_FAILURE; + gsm48_rr_upmsg(ms, msg); + } + + new_rr_state(rr, GSM48_RR_ST_IDLE); + + return 0; + } + + if (rr->state == GSM48_RR_ST_IDLE) { + LOGP(DRR, LOGL_INFO, "MM already released RR.\n"); + + return 0; + } + + LOGP(DRR, LOGL_INFO, "RANDOM ACCESS (requests left %d)\n", + rr->n_chan_req); + + if (!rr->n_chan_req) { + LOGP(DRR, LOGL_INFO, "Done with sending RANDOM ACCESS " + "bursts\n"); + if (!bsc_timer_pending(&rr->t3126)) + start_rr_t3126(rr, 5, 0); /* TODO improve! */ + return 0; + } + rr->n_chan_req--; + + if (rr->wait_assign == 0) { + /* first random acces, without delay of slots */ + slots = 0; + rr->wait_assign = 1; + } else { + /* subsequent random acces, with slots from table 3.1 */ + switch(s->tx_integer) { + case 3: case 8: case 14: case 50: + if (s->ccch_conf != 1) /* not combined CCCH */ + slots = 55; + else + slots = 41; + break; + case 4: case 9: case 16: + if (s->ccch_conf != 1) + slots = 76; + else + slots = 52; + break; + case 5: case 10: case 20: + if (s->ccch_conf != 1) + slots = 109; + else + slots = 58; + break; + case 6: case 11: case 25: + if (s->ccch_conf != 1) + slots = 163; + else + slots = 86; + break; + default: + if (s->ccch_conf != 1) + slots = 217; + else + slots = 115; + break; + } + } + + chan_req = random(); + chan_req &= rr->chan_req_mask; + chan_req |= rr->chan_req_val; + + LOGP(DRR, LOGL_INFO, "RANDOM ACCESS (Tx-integer %d combined %s " + "S(lots) %d ra 0x%02x)\n", s->tx_integer, + (s->ccch_conf == 1) ? "yes": "no", slots, chan_req); + + /* (re)send CHANNEL RQD with new randiom */ + nmsg = gsm48_rsl_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ncch = (struct abis_rsl_cchan_hdr *) msgb_put(nmsg, sizeof(*ncch) + + 4 + 2 + 2); + rsl_init_cchan_hdr(ncch, RSL_MT_CHAN_RQD); + ncch->chan_nr = RSL_CHAN_RACH; + ncch->data[0] = RSL_IE_REQ_REFERENCE; + ncch->data[1] = chan_req; +#warning HACK: fn51 and fn_off + ncch->data[2] = (s->ccch_conf == 1) ? 27 : 50; + ncch->data[3] = 1 + ((random() % s->tx_integer) + slots) / 51; + ncch->data[4] = RSL_IE_ACCESS_DELAY; + ncch->data[5] = set->alter_delay; /* (-)=earlier (+)=later */ + ncch->data[6] = RSL_IE_MS_POWER; + if (set->alter_tx_power) { + tx_power = set->alter_tx_power_value; + LOGP(DRR, LOGL_INFO, "Use alternative tx-power %d (%d dBm)\n", + tx_power, + ms_pwr_dbm(gsm_arfcn2band(cs->arfcn), tx_power)); + } else { + tx_power = s->ms_txpwr_max_cch; + /* power offset in case of DCS1800 */ + if (s->po && cs->arfcn >= 512 && cs->arfcn <= 885) { + LOGP(DRR, LOGL_INFO, "Use MS-TXPWR-MAX-CCH power value " + "%d (%d dBm) with offset %d dBm\n", tx_power, + ms_pwr_dbm(gsm_arfcn2band(cs->arfcn), tx_power), + s->po_value * 2); + /* use reserved bits 7,8 for offset (+ X * 2dB) */ + tx_power |= s->po_value << 6; + } else + LOGP(DRR, LOGL_INFO, "Use MS-TXPWR-MAX-CCH power value " + "%d (%d dBm)\n", tx_power, + ms_pwr_dbm(gsm_arfcn2band(cs->arfcn), + tx_power)); + } + ncch->data[7] = tx_power; + + /* set initial indications */ + rr->ind_tx_power = s->ms_txpwr_max_cch; + rr->ind_ta = set->alter_delay; + + /* store ra until confirmed, then copy it with time into cr_hist */ + rr->cr_ra = chan_req; + + return rslms_recvmsg(nmsg, ms); +} + +/* + * system information + */ + +/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */ +static int gsm48_decode_freq_list(struct gsm_support *sup, + struct gsm_sysinfo_freq *f, uint8_t *cd, uint8_t len, uint8_t mask, + uint8_t frqt) +{ + int i; + + /* NOTES: + * + * The Range format uses "SMOD" computation. + * e.g. "n SMOD m" equals "((n - 1) % m) + 1" + * A cascade of multiple SMOD computations is simpified: + * "(n SMOD m) SMOD o" equals "(((n - 1) % m) % o) + 1" + * + * The Range format uses 16 octets of data in SYSTEM INFORMATION. + * When used in dedicated messages, the length can be less. + * In this case the ranges are decoded for all frequencies that + * fit in the block of given length. + */ + + /* tabula rasa */ + for (i = 0; i < 1024; i++) + f[i].mask &= ~frqt; + + /* 00..XXX. */ + if ((cd[0] & 0xc0 & mask) == 0x00) { + /* Bit map 0 format */ + if (len < 16) + return -EINVAL; + for (i = 1; i <= 124; i++) + if ((cd[15 - ((i-1) >> 3)] & (1 << ((i-1) & 7)))) + f[i].mask |= frqt; + + return 0; + } + + /* only Bit map 0 format for P-GSM */ + if (sup->p_gsm && !sup->e_gsm && !sup->r_gsm && !sup->dcs_1800) + return 0; + + /* 10..0XX. */ + if ((cd[0] & 0xc8 & mask) == 0x80) { + /* Range 1024 format */ + uint16_t w[17]; /* 1..16 */ + struct gsm48_range_1024 *r = (struct gsm48_range_1024 *)cd; + + if (len < 2) + return -EINVAL; + memset(w, 0, sizeof(w)); + if (r->f0) + f[0].mask |= frqt; + w[1] = (r->w1_hi << 8) | r->w1_lo; + if (len >= 4) + w[2] = (r->w2_hi << 1) | r->w2_lo; + if (len >= 5) + w[3] = (r->w3_hi << 2) | r->w3_lo; + if (len >= 6) + w[4] = (r->w4_hi << 2) | r->w4_lo; + if (len >= 7) + w[5] = (r->w5_hi << 2) | r->w5_lo; + if (len >= 8) + w[6] = (r->w6_hi << 2) | r->w6_lo; + if (len >= 9) + w[7] = (r->w7_hi << 2) | r->w7_lo; + if (len >= 10) + w[8] = (r->w8_hi << 1) | r->w8_lo; + if (len >= 10) + w[9] = r->w9; + if (len >= 11) + w[10] = r->w10; + if (len >= 12) + w[11] = (r->w11_hi << 6) | r->w11_lo; + if (len >= 13) + w[12] = (r->w12_hi << 5) | r->w12_lo; + if (len >= 14) + w[13] = (r->w13_hi << 4) | r->w13_lo; + if (len >= 15) + w[14] = (r->w14_hi << 3) | r->w14_lo; + if (len >= 16) + w[15] = (r->w15_hi << 2) | r->w15_lo; + if (len >= 16) + w[16] = r->w16; + if (w[1]) + f[w[1]].mask |= frqt; + if (w[2]) + f[((w[1] - 512 + w[2] - 1) % 1023) + 1].mask |= frqt; + if (w[3]) + f[((w[1] + w[3] - 1) % 1023) + 1].mask |= frqt; + if (w[4]) + f[((w[1] - 512 + ((w[2] - 256 + w[4] - 1) % 511)) % 1023) + 1].mask |= frqt; + if (w[5]) + f[((w[1] + ((w[3] - 256 - w[5] - 1) % 511)) % 1023) + 1].mask |= frqt; + if (w[6]) + f[((w[1] - 512 + ((w[2] + w[6] - 1) % 511)) % 1023) + 1].mask |= frqt; + if (w[7]) + f[((w[1] + ((w[3] + w[7] - 1) % 511)) % 1023) + 1].mask |= frqt; + if (w[8]) + f[((w[1] - 512 + ((w[2] - 256 + ((w[4] - 128 + w[8] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[9]) + f[((w[1] + ((w[3] - 256 + ((w[5] - 128 + w[9] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[10]) + f[((w[1] - 512 + ((w[2] + ((w[6] - 128 + w[10] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[11]) + f[((w[1] + ((w[3] + ((w[7] - 128 + w[11] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[12]) + f[((w[1] - 512 + ((w[2] - 256 + ((w[4] + w[12] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[13]) + f[((w[1] + ((w[3] - 256 + ((w[5] + w[13] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[14]) + f[((w[1] - 512 + ((w[2] + ((w[6] + w[14] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[15]) + f[((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt; + if (w[16]) + f[((w[1] - 512 + ((w[2] - 256 + ((w[4] - 128 + ((w[8] - 64 + w[16] - 1) % 127)) % 255)) % 511)) % 1023) + 1].mask |= frqt; + + return 0; + } + /* 10..100. */ + if ((cd[0] & 0xce & mask) == 0x88) { + /* Range 512 format */ + uint16_t w[18]; /* 1..17 */ + struct gsm48_range_512 *r = (struct gsm48_range_512 *)cd; + + if (len < 4) + return -EINVAL; + memset(w, 0, sizeof(w)); + w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo; + w[1] = (r->w1_hi << 2) | r->w1_lo; + if (len >= 5) + w[2] = (r->w2_hi << 2) | r->w2_lo; + if (len >= 6) + w[3] = (r->w3_hi << 2) | r->w3_lo; + if (len >= 7) + w[4] = (r->w4_hi << 1) | r->w4_lo; + if (len >= 7) + w[5] = r->w5; + if (len >= 8) + w[6] = r->w6; + if (len >= 9) + w[7] = (r->w7_hi << 6) | r->w7_lo; + if (len >= 10) + w[8] = (r->w8_hi << 4) | r->w8_lo; + if (len >= 11) + w[9] = (r->w9_hi << 2) | r->w9_lo; + if (len >= 11) + w[10] = r->w10; + if (len >= 12) + w[11] = r->w11; + if (len >= 13) + w[12] = (r->w12_hi << 4) | r->w12_lo; + if (len >= 14) + w[13] = (r->w13_hi << 2) | r->w13_lo; + if (len >= 14) + w[14] = r->w14; + if (len >= 15) + w[15] = r->w15; + if (len >= 16) + w[16] = (r->w16_hi << 3) | r->w16_lo; + if (len >= 16) + w[17] = r->w17; + f[w[0]].mask |= frqt; + if (w[1]) + f[(w[0] + w[1]) % 1024].mask |= frqt; + if (w[2]) + f[(w[0] + ((w[1] - 256 + w[2] - 1) % 511) + 1) % 1024].mask |= frqt; + if (w[3]) + f[(w[0] + ((w[1] + w[3] - 1) % 511) + 1) % 1024].mask |= frqt; + if (w[4]) + f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + w[4] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[5]) + f[(w[0] + ((w[1] + ((w[3] - 128 + w[5] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[6]) + f[(w[0] + ((w[1] - 256 + ((w[2] + w[6] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[7]) + f[(w[0] + ((w[1] + ((w[3] + w[7] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[8]) + f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] - 64 + w[8] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[9]) + f[(w[0] + ((w[1] + ((w[3] - 128 + ((w[5] - 64 + w[9] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[10]) + f[(w[0] + ((w[1] - 256 + ((w[2] + ((w[6] - 64 + w[10] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[11]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 64 + w[11] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[12]) + f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] + w[12] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[13]) + f[(w[0] + ((w[1] + ((w[3] - 128 + ((w[5] + w[13] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[14]) + f[(w[0] + ((w[1] - 256 + ((w[2] + ((w[6] + w[14] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[15]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[16]) + f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] - 64 + ((w[8] - 32 + w[16] - 1) % 63)) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + if (w[17]) + f[(w[0] + ((w[1] + ((w[3] - 128 + ((w[5] - 64 + ((w[9] - 32 + w[17] - 1) % 63)) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt; + + return 0; + } + /* 10..101. */ + if ((cd[0] & 0xce & mask) == 0x8a) { + /* Range 256 format */ + uint16_t w[22]; /* 1..21 */ + struct gsm48_range_256 *r = (struct gsm48_range_256 *)cd; + + if (len < 4) + return -EINVAL; + memset(w, 0, sizeof(w)); + w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo; + w[1] = (r->w1_hi << 1) | r->w1_lo; + if (len >= 4) + w[2] = r->w2; + if (len >= 5) + w[3] = r->w3; + if (len >= 6) + w[4] = (r->w4_hi << 5) | r->w4_lo; + if (len >= 7) + w[5] = (r->w5_hi << 3) | r->w5_lo; + if (len >= 8) + w[6] = (r->w6_hi << 1) | r->w6_lo; + if (len >= 8) + w[7] = r->w7; + if (len >= 9) + w[8] = (r->w8_hi << 4) | r->w8_lo; + if (len >= 10) + w[9] = (r->w9_hi << 1) | r->w9_lo; + if (len >= 10) + w[10] = r->w10; + if (len >= 11) + w[11] = (r->w11_hi << 3) | r->w11_lo; + if (len >= 11) + w[12] = r->w12; + if (len >= 12) + w[13] = r->w13; + if (len >= 13) + w[14] = r->w15; + if (len >= 13) + w[15] = (r->w14_hi << 2) | r->w14_lo; + if (len >= 14) + w[16] = (r->w16_hi << 3) | r->w16_lo; + if (len >= 14) + w[17] = r->w17; + if (len >= 15) + w[18] = r->w19; + if (len >= 15) + w[19] = (r->w18_hi << 3) | r->w18_lo; + if (len >= 16) + w[20] = (r->w20_hi << 3) | r->w20_lo; + if (len >= 16) + w[21] = r->w21; + f[w[0]].mask |= frqt; + if (w[1]) + f[(w[0] + w[1]) % 1024].mask |= frqt; + if (w[2]) + f[(w[0] + ((w[1] - 128 + w[2] - 1) % 255) + 1) % 1024].mask |= frqt; + if (w[3]) + f[(w[0] + ((w[1] + w[3] - 1) % 255) + 1) % 1024].mask |= frqt; + if (w[4]) + f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + w[4] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[5]) + f[(w[0] + ((w[1] + ((w[3] - 64 + w[5] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[6]) + f[(w[0] + ((w[1] - 128 + ((w[2] + w[6] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[7]) + f[(w[0] + ((w[1] + ((w[3] + w[7] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[8]) + f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] - 32 + w[8] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[9]) + f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] - 32 + w[9] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[10]) + f[(w[0] + ((w[1] - 128 + ((w[2] + ((w[6] - 32 + w[10] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[11]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 32 + w[11] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[12]) + f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] + w[12] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[13]) + f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] + w[13] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[14]) + f[(w[0] + ((w[1] - 128 + ((w[2] + ((w[6] + w[14] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[15]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[16]) + f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] - 32 + ((w[8] - 16 + w[16] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[17]) + f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] - 32 + ((w[9] - 16 + w[17] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[18]) + f[(w[0] + ((w[1] - 128 + ((w[2] + ((w[6] - 32 + ((w[10] - 16 + w[18] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[19]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 32 + ((w[11] - 16 + w[19] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[20]) + f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] + ((w[12] - 16 + w[20] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + if (w[21]) + f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] + ((w[13] - 16 + w[21] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt; + + return 0; + } + /* 10..110. */ + if ((cd[0] & 0xce & mask) == 0x8c) { + /* Range 128 format */ + uint16_t w[29]; /* 1..28 */ + struct gsm48_range_128 *r = (struct gsm48_range_128 *)cd; + + if (len < 3) + return -EINVAL; + memset(w, 0, sizeof(w)); + w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo; + w[1] = r->w1; + if (len >= 4) + w[2] = r->w2; + if (len >= 5) + w[3] = (r->w3_hi << 4) | r->w3_lo; + if (len >= 6) + w[4] = (r->w4_hi << 1) | r->w4_lo; + if (len >= 6) + w[5] = r->w5; + if (len >= 7) + w[6] = (r->w6_hi << 3) | r->w6_lo; + if (len >= 7) + w[7] = r->w7; + if (len >= 8) + w[8] = r->w8; + if (len >= 8) + w[9] = r->w9; + if (len >= 9) + w[10] = r->w10; + if (len >= 9) + w[11] = r->w11; + if (len >= 10) + w[12] = r->w12; + if (len >= 10) + w[13] = r->w13; + if (len >= 11) + w[14] = r->w14; + if (len >= 11) + w[15] = r->w15; + if (len >= 12) + w[16] = r->w16; + if (len >= 12) + w[17] = r->w17; + if (len >= 13) + w[18] = (r->w18_hi << 1) | r->w18_lo; + if (len >= 13) + w[19] = r->w19; + if (len >= 13) + w[20] = r->w20; + if (len >= 14) + w[21] = (r->w21_hi << 2) | r->w21_lo; + if (len >= 14) + w[22] = r->w22; + if (len >= 14) + w[23] = r->w23; + if (len >= 15) + w[24] = r->w24; + if (len >= 15) + w[25] = r->w25; + if (len >= 16) + w[26] = (r->w26_hi << 1) | r->w26_lo; + if (len >= 16) + w[27] = r->w27; + if (len >= 16) + w[28] = r->w28; + f[w[0]].mask |= frqt; + if (w[1]) + f[(w[0] + w[1]) % 1024].mask |= frqt; + if (w[2]) + f[(w[0] + ((w[1] - 64 + w[2] - 1) % 127) + 1) % 1024].mask |= frqt; + if (w[3]) + f[(w[0] + ((w[1] + w[3] - 1) % 127) + 1) % 1024].mask |= frqt; + if (w[4]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + w[4] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[5]) + f[(w[0] + ((w[1] + ((w[3] - 32 + w[5] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[6]) + f[(w[0] + ((w[1] - 64 + ((w[2] + w[6] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[7]) + f[(w[0] + ((w[1] + ((w[3] + w[7] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[8]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + w[8] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[9]) + f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] - 16 + w[9] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[10]) + f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] - 16 + w[10] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[11]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 16 + w[11] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[12]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] + w[12] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[13]) + f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] + w[13] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[14]) + f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] + w[14] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[15]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[16]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + ((w[8] - 8 + w[16] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[17]) + f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] - 16 + ((w[9] - 8 + w[17] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[18]) + f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] - 16 + ((w[10] - 8 + w[18] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[19]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 16 + ((w[11] - 8 + w[19] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[20]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] + ((w[12] - 8 + w[20] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[21]) + f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] + ((w[13] - 8 + w[21] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[22]) + f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] + ((w[14] - 8 + w[22] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[23]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] + ((w[15] - 8 + w[23] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[24]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + ((w[8] + w[24] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[25]) + f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] - 16 + ((w[9] + w[25] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[26]) + f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] - 16 + ((w[10] + w[26] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[27]) + f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 16 + ((w[11] + w[27] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + if (w[28]) + f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] + ((w[12] + w[28] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt; + + return 0; + } + /* 10..111. */ + if ((cd[0] & 0xce & mask) == 0x8e) { + /* Variable bitmap format (can be any length >= 3) */ + uint16_t orig = 0; + struct gsm48_var_bit *r = (struct gsm48_var_bit *)cd; + + if (len < 3) + return -EINVAL; + orig = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo; + f[orig].mask |= frqt; + for (i = 1; 2 + (i >> 3) < len; i++) + if ((cd[2 + (i >> 3)] & (0x80 >> (i & 7)))) + f[(orig + i) % 1024].mask |= frqt; + + return 0; + } + + return 0; +} + +/* decode "Cell Selection Parameters" (10.5.2.4) */ +static int gsm48_decode_cell_sel_param(struct gsm48_sysinfo *s, + struct gsm48_cell_sel_par *cs) +{ +#ifdef TODO + convert ms_txpwr_max_ccch dependant on the current frequenc and support + to the right powe level +#endif + s->ms_txpwr_max_cch = cs->ms_txpwr_max_ccch; + s->cell_resel_hyst_db = cs->cell_resel_hyst * 2; + s->rxlev_acc_min_db = cs->rxlev_acc_min - 110; + s->neci = cs->neci; + s->acs = cs->acs; + + return 0; +} + +/* decode "Cell Options (BCCH)" (10.5.2.3) */ +static int gsm48_decode_cellopt_bcch(struct gsm48_sysinfo *s, + struct gsm48_cell_options *co) +{ + s->bcch_radio_link_timeout = (co->radio_link_timeout + 1) * 4; + s->bcch_dtx = co->dtx; + s->bcch_pwrc = co->pwrc; + + return 0; +} + +/* decode "Cell Options (SACCH)" (10.5.2.3a) */ +static int gsm48_decode_cellopt_sacch(struct gsm48_sysinfo *s, + struct gsm48_cell_options *co) +{ + s->sacch_radio_link_timeout = (co->radio_link_timeout + 1) * 4; + s->sacch_dtx = co->dtx; + s->sacch_pwrc = co->pwrc; + + return 0; +} + +/* decode "Control Channel Description" (10.5.2.11) */ +static int gsm48_decode_ccd(struct gsm48_sysinfo *s, + struct gsm48_control_channel_descr *cc) +{ + s->ccch_conf = cc->ccch_conf; + s->bs_ag_blks_res = cc->bs_ag_blks_res; + s->att_allowed = cc->att; + s->pag_mf_periods = cc->bs_pa_mfrms + 2; + s->t3212 = cc->t3212 * 360; /* convert deci-hours to seconds */ + + return 0; +} + +/* decode "Mobile Allocation" (10.5.2.21) */ +static int gsm48_decode_mobile_alloc(struct gsm48_sysinfo *s, + uint8_t *ma, uint8_t len, uint16_t *hopping, uint8_t *hopp_len, int si4) +{ + int i, j = 0; + uint16_t f[len << 3]; + + /* not more than 64 hopping indexes allowed in IE */ + if (len > 8) + return -EINVAL; + + /* tabula rasa */ + *hopp_len = 0; + if (si4) { + for (i = 0; i < 1024; i++) + s->freq[i].mask &= ~FREQ_TYPE_HOPP; + } + + /* generating list of all frequencies (1..1023,0) */ + for (i = 1; i <= 1024; i++) { + if ((s->freq[i & 1023].mask & FREQ_TYPE_SERV)) { + LOGP(DRR, LOGL_INFO, "Serving cell ARFCN #%d: %d\n", + j, i & 1023); + f[j++] = i & 1023; + if (j == (len << 3)) + break; + } + } + + /* fill hopping table with frequency index given by IE + * and set hopping type bits + */ + for (i = 0; i < (len << 3); i++) { + /* if bit is set, this frequency index is used for hopping */ + if ((ma[len - 1 - (i >> 3)] & (1 << (i & 7)))) { + LOGP(DRR, LOGL_INFO, "Hopping ARFCN: %d (bit %d)\n", + i, f[i]); + /* index higher than entries in list ? */ + if (i >= j) { + LOGP(DRR, LOGL_NOTICE, "Mobile Allocation " + "hopping index %d exceeds maximum " + "number of cell frequencies. (%d)\n", + i + 1, j); + break; + } + hopping[(*hopp_len)++] = f[i]; + if (si4) + s->freq[f[i]].mask |= FREQ_TYPE_HOPP; + } + } + + return 0; +} + +/* Rach Control decode tables */ +static uint8_t gsm48_max_retrans[4] = { + 1, 2, 4, 7 +}; +static uint8_t gsm48_tx_integer[16] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50 +}; + +/* decode "RACH Control Parameter" (10.5.2.29) */ +static int gsm48_decode_rach_ctl_param(struct gsm48_sysinfo *s, + struct gsm48_rach_control *rc) +{ + s->reest_denied = rc->re; + s->cell_barr = rc->cell_bar; + s->tx_integer = gsm48_tx_integer[rc->tx_integer]; + s->max_retrans = gsm48_max_retrans[rc->max_trans]; + s->class_barr = (rc->t2 << 8) | rc->t3; + + return 0; +} +static int gsm48_decode_rach_ctl_neigh(struct gsm48_sysinfo *s, + struct gsm48_rach_control *rc) +{ + s->nb_reest_denied = rc->re; + s->nb_cell_barr = rc->cell_bar; + s->nb_tx_integer = gsm48_tx_integer[rc->tx_integer]; + s->nb_max_retrans = gsm48_max_retrans[rc->max_trans]; + s->nb_class_barr = (rc->t2 << 8) | rc->t3; + + return 0; +} + +/* decode "SI 1 Rest Octets" (10.5.2.32) */ +static int gsm48_decode_si1_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + return 0; +} + +/* decode "SI 3 Rest Octets" (10.5.2.34) */ +static int gsm48_decode_si3_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data_len = len; + bv.data = si; + + /* Optional Selection Parameters */ + if (bitvec_get_bit_high(&bv) == H) { + s->sp = 1; + s->sp_cbq = bitvec_get_uint(&bv, 1); + s->sp_cro = bitvec_get_uint(&bv, 6); + s->sp_to = bitvec_get_uint(&bv, 3); + s->sp_pt = bitvec_get_uint(&bv, 5); + } + /* Optional Power Offset */ + if (bitvec_get_bit_high(&bv) == H) { + s->po = 1; + s->po_value = bitvec_get_uint(&bv, 3); + } + /* System Onformation 2ter Indicator */ + if (bitvec_get_bit_high(&bv) == H) + s->si2ter_ind = 1; + /* Early Classark Sending Control */ + if (bitvec_get_bit_high(&bv) == H) + s->ecsm = 1; + /* Scheduling if and where */ + if (bitvec_get_bit_high(&bv) == H) { + s->sched = 1; + s->sched_where = bitvec_get_uint(&bv, 3); + } + /* GPRS Indicator */ + s->gi_ra_colour = bitvec_get_uint(&bv, 3); + s->gi_si13_pos = bitvec_get_uint(&bv, 1); + return 0; +} + +/* decode "SI 4 Rest Octets" (10.5.2.35) */ +static int gsm48_decode_si4_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + return 0; +} + +/* decode "SI 6 Rest Octets" (10.5.2.35a) */ +static int gsm48_decode_si6_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + return 0; +} + +/* send sysinfo event to other layers */ +static int gsm48_send_sysinfo(struct osmocom_ms *ms, uint8_t type) +{ + struct msgb *nmsg; + struct gsm322_msg *em; + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SYSINFO); + if (!nmsg) + return -ENOMEM; + em = (struct gsm322_msg *) nmsg->data; + em->sysinfo = type; + gsm322_cs_sendmsg(ms, nmsg); + + /* send timer info to location update process */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_SYSINFO); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + return 0; +} + +/* receive "SYSTEM INFORMATION 1" message (9.1.31) */ +static int gsm48_rr_rx_sysinfo1(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_1 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 1 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 1 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si1_msg, MIN(msgb_l3len(msg), sizeof(s->si1_msg)))) + return 0; + memcpy(s->si1_msg, si, MIN(msgb_l3len(msg), sizeof(s->si1_msg))); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n"); + + /* Cell Channel Description */ + gsm48_decode_freq_list(&ms->support, s->freq, + si->cell_channel_description, + sizeof(si->cell_channel_description), 0xce, FREQ_TYPE_SERV); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_param(s, &si->rach_control); + /* SI 1 Rest Octets */ + if (payload_len) + gsm48_decode_si1_rest(s, si->rest_octets, payload_len); + + s->si1 = 1; + + return gsm48_send_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 2" message (9.1.32) */ +static int gsm48_rr_rx_sysinfo2(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_2 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 2 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2 " + "message.\n"); + return -EINVAL; + } +//printf("len = %d\n", MIN(msgb_l3len(msg), sizeof(s->si2_msg))); + + if (!memcmp(si, s->si2_msg, MIN(msgb_l3len(msg), sizeof(s->si2_msg)))) + return 0; + memcpy(s->si2_msg, si, MIN(msgb_l3len(msg), sizeof(s->si2_msg))); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2\n"); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si2 = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si2 = (si->bcch_frequency_list[0] >> 5) & 1; + gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2); + /* NCC Permitted */ + s->nb_ncc_permitted = si->ncc_permitted; + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_neigh(s, &si->rach_control); + + s->si2 = 1; + + return gsm48_send_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 2bis" message (9.1.33) */ +static int gsm48_rr_rx_sysinfo2bis(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_2bis *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 2bis" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2bis " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si2b_msg, MIN(msgb_l3len(msg), + sizeof(s->si2b_msg)))) + return 0; + memcpy(s->si2b_msg, si, MIN(msgb_l3len(msg), sizeof(s->si2b_msg))); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2bis\n"); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si2bis = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si2bis = (si->bcch_frequency_list[0] >> 5) & 1; + gsm48_decode_freq_list(&ms->support, s->freq, + si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0x8e, + FREQ_TYPE_NCELL_2bis); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_neigh(s, &si->rach_control); + + s->si2bis = 1; + + return gsm48_send_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 2ter" message (9.1.34) */ +static int gsm48_rr_rx_sysinfo2ter(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_2ter *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 2ter" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2ter " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si2t_msg, MIN(msgb_l3len(msg), + sizeof(s->si2t_msg)))) + return 0; + memcpy(s->si2t_msg, si, MIN(msgb_l3len(msg), sizeof(s->si2t_msg))); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2ter\n"); + + /* Neighbor Cell Description 2 */ + s->nb_multi_rep_si2ter = (si->ext_bcch_frequency_list[0] >> 6) & 3; + gsm48_decode_freq_list(&ms->support, s->freq, + si->ext_bcch_frequency_list, + sizeof(si->ext_bcch_frequency_list), 0x8e, + FREQ_TYPE_NCELL_2ter); + + s->si2ter = 1; + + return gsm48_send_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 3" message (9.1.35) */ +static int gsm48_rr_rx_sysinfo3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_3 *si = msgb_l3(msg); + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 3 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 3 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si3_msg, MIN(msgb_l3len(msg), sizeof(s->si3_msg)))) + return 0; + memcpy(s->si3_msg, si, MIN(msgb_l3len(msg), sizeof(s->si3_msg))); + + /* Cell Identity */ + s->cell_id = ntohs(si->cell_identity); + /* LAI */ + gsm48_decode_lai(&si->lai, &s->mcc, &s->mnc, &s->lac); + /* Control Channel Description */ + gsm48_decode_ccd(s, &si->control_channel_desc); + /* Cell Options (BCCH) */ + gsm48_decode_cellopt_bcch(s, &si->cell_options); + /* Cell Selection Parameters */ + gsm48_decode_cell_sel_param(s, &si->cell_sel_par); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_param(s, &si->rach_control); + /* SI 3 Rest Octets */ + if (payload_len >= 4) + gsm48_decode_si3_rest(s, si->rest_octets, payload_len); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 3 (mcc %s mnc %s " + "lac 0x%04x)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + + s->si3 = 1; + + if (cs->ccch_mode == CCCH_MODE_NONE) { + cs->ccch_mode = (s->ccch_conf == 1) ? CCCH_MODE_COMBINED : + CCCH_MODE_NON_COMBINED; + LOGP(DRR, LOGL_NOTICE, "Changing CCCH_MODE to %d\n", + cs->ccch_mode); + l1ctl_tx_ccch_mode_req(ms, cs->ccch_mode); + } + + return gsm48_send_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 4" message (9.1.36) */ +static int gsm48_rr_rx_sysinfo4(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_4 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + uint8_t *data = si->data; + struct gsm48_chan_desc *cd; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 4 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + short_read: + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 4 " + "message.\n"); + return -EINVAL; + } + + if (!s->si1) { + LOGP(DRR, LOGL_NOTICE, "Ignoring SYSTEM INFORMATION 4 " + "until SI 1 is received.\n"); + return -EBUSY; + } + + if (!memcmp(si, s->si4_msg, MIN(msgb_l3len(msg), sizeof(s->si4_msg)))) + return 0; + memcpy(s->si4_msg, si, MIN(msgb_l3len(msg), sizeof(s->si4_msg))); + + /* LAI */ + gsm48_decode_lai(&si->lai, &s->mcc, &s->mnc, &s->lac); + /* Cell Selection Parameters */ + gsm48_decode_cell_sel_param(s, &si->cell_sel_par); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_param(s, &si->rach_control); + /* CBCH Channel Description */ + if (payload_len >= 1 && data[0] == GSM48_IE_CBCH_CHAN_DESC) { + if (payload_len < 4) + goto short_read; + cd = (struct gsm48_chan_desc *) (data + 1); + if (cd->h0.h) { + s->h = 1; + gsm48_decode_chan_h1(cd, &s->tsc, &s->maio, &s->hsn); + } else { + s->h = 0; + gsm48_decode_chan_h0(cd, &s->tsc, &s->arfcn); + } + payload_len -= 4; + data += 4; + } + /* CBCH Mobile Allocation */ + if (payload_len >= 1 && data[0] == GSM48_IE_CBCH_MOB_AL) { + if (payload_len < 1 || payload_len < 2 + data[1]) + goto short_read; + gsm48_decode_mobile_alloc(s, data + 2, si->data[1], s->hopping, + &s->hopp_len, 1); + payload_len -= 2 + data[1]; + data += 2 + data[1]; + } + /* SI 4 Rest Octets */ + if (payload_len > 0) + gsm48_decode_si4_rest(s, data, payload_len); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4 (mcc %s mnc %s " + "lac 0x%04x)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + + s->si4 = 1; + + return gsm48_send_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 5" message (9.1.37) */ +static int gsm48_rr_rx_sysinfo5(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5 *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si5_msg, MIN(msgb_l3len(msg), sizeof(s->si5_msg)))) + return 0; + memcpy(s->si5_msg, si, MIN(msgb_l3len(msg), sizeof(s->si5_msg))); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5\n"); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si5 = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si5 = (si->bcch_frequency_list[0] >> 5) & 1; + gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5); + + s->si5 = 1; + + return gsm48_send_sysinfo(ms, si->system_information); +} + +/* receive "SYSTEM INFORMATION 5bis" message (9.1.38) */ +static int gsm48_rr_rx_sysinfo5bis(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5bis *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5bis" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5bis " + "message.\n"); + return -EINVAL; + } + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5bis\n"); + + if (!memcmp(si, s->si5b_msg, MIN(msgb_l3len(msg), + sizeof(s->si5b_msg)))) + return 0; + memcpy(s->si5b_msg, si, MIN(msgb_l3len(msg), sizeof(s->si5b_msg))); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si5bis = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si5bis = (si->bcch_frequency_list[0] >> 5) & 1; + gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5bis); + + s->si5bis = 1; + + return gsm48_send_sysinfo(ms, si->system_information); +} + +/* receive "SYSTEM INFORMATION 5ter" message (9.1.39) */ +static int gsm48_rr_rx_sysinfo5ter(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5ter *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5ter" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5ter " + "message.\n"); + return -EINVAL; + } + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5ter\n"); + + if (!memcmp(si, s->si5t_msg, MIN(msgb_l3len(msg), + sizeof(s->si5t_msg)))) + return 0; + memcpy(s->si5t_msg, si, MIN(msgb_l3len(msg), sizeof(s->si5t_msg))); + + /* Neighbor Cell Description */ + gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5ter); + + s->si5ter = 1; + + return gsm48_send_sysinfo(ms, si->system_information); +} + +/* receive "SYSTEM INFORMATION 6" message (9.1.39) */ +static int gsm48_rr_rx_sysinfo6(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_6 *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 6 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 6 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si6_msg, MIN(msgb_l3len(msg), sizeof(s->si6_msg)))) + return 0; + memcpy(s->si6_msg, si, MIN(msgb_l3len(msg), sizeof(s->si6_msg))); + + /* Cell Identity */ + if (s->si6 && s->cell_id != ntohs(si->cell_identity)) + LOGP(DRR, LOGL_INFO, "Cell ID on SI 6 differs from previous " + "read.\n"); + s->cell_id = ntohs(si->cell_identity); + /* LAI */ + gsm48_decode_lai(&si->lai, &s->mcc, &s->mnc, &s->lac); + /* Cell Options (SACCH) */ + gsm48_decode_cellopt_sacch(s, &si->cell_options); + /* NCC Permitted */ + s->nb_ncc_permitted = si->ncc_permitted; + /* SI 6 Rest Octets */ + if (payload_len >= 4) + gsm48_decode_si6_rest(s, si->rest_octets, payload_len); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 6 (mcc %s mnc %s " + "lac 0x%04x SACCH-timeout %d)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac, s->sacch_radio_link_timeout); + + s->si6 = 1; + + return gsm48_send_sysinfo(ms, si->system_information); +} + +/* + * paging + */ + +/* paging channel request */ +static int gsm48_rr_chan2cause[4] = { + RR_EST_CAUSE_ANS_PAG_ANY, + RR_EST_CAUSE_ANS_PAG_SDCCH, + RR_EST_CAUSE_ANS_PAG_TCH_F, + RR_EST_CAUSE_ANS_PAG_TCH_ANY +}; + +/* given LV of mobile identity is checked agains ms */ +static int gsm_match_mi(struct osmocom_ms *ms, uint8_t *mi) +{ + char imsi[16]; + uint32_t tmsi; + uint8_t mi_type; + + if (mi[0] < 1) + return 0; + mi_type = mi[1] & GSM_MI_TYPE_MASK; + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + if (mi[0] < 5) + return 0; + memcpy(&tmsi, mi+2, 4); + if (ms->subscr.tmsi == ntohl(tmsi) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", + ntohl(tmsi)); + + return 1; + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(tmsi)); + break; + case GSM_MI_TYPE_IMSI: + gsm48_mi_to_string(imsi, sizeof(imsi), mi + 1, mi[0]); + if (!strcmp(imsi, ms->subscr.imsi)) { + LOGP(DPAG, LOGL_INFO, "IMSI %s matches\n", imsi); + + return 1; + } else + LOGP(DPAG, LOGL_INFO, "IMSI %s (not for us)\n", imsi); + break; + default: + LOGP(DPAG, LOGL_NOTICE, "Paging with unsupported MI type %d.\n", + mi_type); + } + + return 0; +} + +/* 9.1.22 PAGING REQUEST 1 message received */ +static int gsm48_rr_rx_pag_req_1(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_paging1 *pa = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*pa); + int chan_1, chan_2; + uint8_t *mi; + + /* empty paging request */ + if (payload_len >= 2 && (pa->data[1] & GSM_MI_TYPE_MASK) == 0) + return 0; + + /* 3.3.1.1.2: ignore paging while not camping on a cell */ + if (rr->state != GSM48_RR_ST_IDLE || !cs->selected + || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL)) { + LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping.\n"); + + return 0; + } + LOGP(DPAG, LOGL_INFO, "PAGING REQUEST 1\n"); + + if (payload_len < 2) { + short_read: + LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 1 " + "message.\n"); + + return -EINVAL; + } + + /* channel needed */ + chan_1 = pa->cneed1; + chan_2 = pa->cneed2; + /* first MI */ + mi = pa->data; + if (payload_len < mi[0] + 1) + goto short_read; + if (gsm_match_mi(ms, mi) > 0) + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1); + /* second MI */ + payload_len -= mi[0] + 1; + mi = pa->data + mi[0] + 1; + if (payload_len < 2) + return 0; + if (mi[0] != GSM48_IE_MOBILE_ID) + return 0; + if (payload_len < mi[1] + 2) + goto short_read; + if (gsm_match_mi(ms, mi + 1) > 0) + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1); + + return 0; +} + +/* 9.1.23 PAGING REQUEST 2 message received */ +static int gsm48_rr_rx_pag_req_2(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_paging2 *pa = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*pa); + uint8_t *mi; + int chan_1, chan_2, chan_3; + + /* 3.3.1.1.2: ignore paging while not camping on a cell */ + if (rr->state != GSM48_RR_ST_IDLE || !cs->selected + || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL)) { + LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping.\n"); + + return 0; + } + LOGP(DPAG, LOGL_INFO, "PAGING REQUEST 2\n"); + + if (payload_len < 0) { + short_read: + LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 2 " + "message .\n"); + + return -EINVAL; + } + + /* channel needed */ + chan_1 = pa->cneed1; + chan_2 = pa->cneed2; + /* first MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi1) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", ntohl(pa->tmsi1)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1); + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(pa->tmsi1)); + /* second MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi2) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", ntohl(pa->tmsi2)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1); + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(pa->tmsi2)); + /* third MI */ + mi = pa->data; + if (payload_len < 2) + return 0; + if (mi[0] != GSM48_IE_MOBILE_ID) + return 0; + if (payload_len < mi[1] + 2 + 1) /* must include "channel needed" */ + goto short_read; + chan_3 = mi[mi[1] + 2] & 0x03; /* channel needed */ + if (gsm_match_mi(ms, mi + 1) > 0) + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1); + + return 0; +} + +/* 9.1.24 PAGING REQUEST 3 message received */ +static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_paging3 *pa = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*pa); + int chan_1, chan_2, chan_3, chan_4; + + /* 3.3.1.1.2: ignore paging while not camping on a cell */ + if (rr->state != GSM48_RR_ST_IDLE || !cs->selected + || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL)) { + LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping.\n"); + + return 0; + } + LOGP(DPAG, LOGL_INFO, "PAGING REQUEST 3\n"); + + if (payload_len < 0) { /* must include "channel needed", part of *pa */ + LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 3 " + "message .\n"); + + return -EINVAL; + } + + /* channel needed */ + chan_1 = pa->cneed1; + chan_2 = pa->cneed2; + chan_3 = pa->cneed3; + chan_4 = pa->cneed4; + /* first MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi1) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", ntohl(pa->tmsi1)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1); + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(pa->tmsi1)); + /* second MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi2) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", ntohl(pa->tmsi2)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1); + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(pa->tmsi2)); + /* thrid MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi3) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", ntohl(pa->tmsi3)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1); + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(pa->tmsi3)); + /* fourth MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi4) + && ms->subscr.tmsi_valid) { + LOGP(DPAG, LOGL_INFO, "TMSI %08x matches\n", ntohl(pa->tmsi4)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_4], 1); + } else + LOGP(DPAG, LOGL_INFO, "TMSI %08x (not for us)\n", + ntohl(pa->tmsi4)); + + return 0; +} + +/* + * (immediate) assignment + */ + +/* match request reference agains request history */ +static int gsm48_match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + int i; + uint8_t ia_t1, ia_t2, ia_t3; + uint8_t cr_t1, cr_t2, cr_t3; + + for (i = 0; i < 3; i++) { + /* filter confirmed RACH requests only */ + if (rr->cr_hist[i].valid && ref->ra == rr->cr_hist[i].ref.ra) { + ia_t1 = ref->t1; + ia_t2 = ref->t2; + ia_t3 = (ref->t3_high << 3) | ref->t3_low; + ref = &rr->cr_hist[i].ref; + cr_t1 = ref->t1; + cr_t2 = ref->t2; + cr_t3 = (ref->t3_high << 3) | ref->t3_low; + if (ia_t1 == cr_t1 && ia_t2 == cr_t2 + && ia_t3 == cr_t3) { + LOGP(DRR, LOGL_INFO, "request %02x matches " + "(fn=%d,%d,%d)\n", ref->ra, ia_t1, + ia_t2, ia_t3); + return 1; + } else + LOGP(DRR, LOGL_INFO, "request %02x matches " + "but not frame number (IMM.ASS " + "fn=%d,%d,%d != RACH fn=%d,%d,%d)\n", + ref->ra, ia_t1, ia_t2, ia_t3, + cr_t1, cr_t2, cr_t3); + } + } + + return 0; +} + +/* 9.1.18 IMMEDIATE ASSIGNMENT is received */ +static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_imm_ass *ia = msgb_l3(msg); + int ma_len = msgb_l3len(msg) - sizeof(*ia); + uint8_t ch_type, ch_subch, ch_ts; + struct gsm48_rr_cd cd; + uint8_t *st, st_len; + + memset(&cd, 0, sizeof(cd)); + + if (ma_len < 0 /* mobile allocation IE must be included */ + || ia->mob_alloc_len > ma_len) { /* short read of IE */ + LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT " + "message.\n"); + return -EINVAL; + } + if (ia->mob_alloc_len > 8) { + LOGP(DRR, LOGL_NOTICE, "Moble allocation in IMMEDIATE " + "ASSIGNMENT too large.\n"); + return -EINVAL; + } + + /* starting time */ + st_len = ma_len - ia->mob_alloc_len; + st = ia->mob_alloc + ia->mob_alloc_len; + if (st_len >= 3 && st[0] == GSM48_IE_START_TIME) + gsm48_decode_start_time(&cd, (struct gsm48_start_time *)(st+1)); + + /* decode channel description */ + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT:\n"); + cd.chan_nr = ia->chan_desc.chan_nr; + rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ia->chan_desc.h0.h) { + cd.h = 1; + gsm48_decode_chan_h1(&ia->chan_desc, &cd.tsc, &cd.maio, + &cd.hsn); + LOGP(DRR, LOGL_INFO, " (ta %d/%dm ra 0x%02x chan_nr 0x%02x " + "MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ia->timing_advance, + ia->timing_advance * GSM_TA_CM / 100, + ia->req_ref.ra, ia->chan_desc.chan_nr, cd.maio, + cd.hsn, ch_ts, ch_subch, cd.tsc); + } else { + cd.h = 0; + gsm48_decode_chan_h0(&ia->chan_desc, &cd.tsc, &cd.arfcn); + LOGP(DRR, LOGL_INFO, " (ta %d/%dm ra 0x%02x chan_nr 0x%02x " + "ARFCN %u TS %u SS %u TSC %u)\n", + ia->timing_advance, + ia->timing_advance * GSM_TA_CM / 100, + ia->req_ref.ra, ia->chan_desc.chan_nr, cd.arfcn, + ch_ts, ch_subch, cd.tsc); + } + + /* 3.3.1.1.2: ignore assignment while idle */ + if (rr->state != GSM48_RR_ST_CONN_PEND || rr->wait_assign == 0) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + if (rr->wait_assign == 2) { + LOGP(DRR, LOGL_INFO, "Ignoring, channel already assigned.\n"); + return 0; + } + + /* request ref */ + if (gsm48_match_ra(ms, &ia->req_ref)) { + /* channel description */ + memcpy(&rr->cd_now, &cd, sizeof(rr->cd_now)); + /* timing advance */ + rr->cd_now.ta = ia->timing_advance; + /* mobile allocation */ + memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len, + ia->mob_alloc_len + 1); + rr->wait_assign = 2; + return gsm48_rr_dl_est(ms); + } + LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); + + return 0; +} + +/* 9.1.19 IMMEDIATE ASSIGNMENT EXTENDED is received */ +static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_imm_ass_ext *ia = msgb_l3(msg); + int ma_len = msgb_l3len(msg) - sizeof(*ia); + uint8_t ch_type, ch_subch, ch_ts; + struct gsm48_rr_cd cd1, cd2; + uint8_t *st, st_len; + + memset(&cd1, 0, sizeof(cd1)); + memset(&cd2, 0, sizeof(cd2)); + + if (ma_len < 0 /* mobile allocation IE must be included */ + || ia->mob_alloc_len > ma_len) { /* short read of IE */ + LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT " + "EXTENDED message.\n"); + return -EINVAL; + } + if (ia->mob_alloc_len > 4) { + LOGP(DRR, LOGL_NOTICE, "Moble allocation in IMMEDIATE " + "ASSIGNMENT EXTENDED too large.\n"); + return -EINVAL; + } + + /* starting time */ + st_len = ma_len - ia->mob_alloc_len; + st = ia->mob_alloc + ia->mob_alloc_len; + if (st_len >= 3 && st[0] == GSM48_IE_START_TIME) { + gsm48_decode_start_time(&cd1, + (struct gsm48_start_time *)(st+1)); + memcpy(&cd2, &cd1, sizeof(cd2)); + } + + /* decode channel description */ + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT EXTENDED:\n"); + cd2.chan_nr = ia->chan_desc1.chan_nr; + rsl_dec_chan_nr(cd1.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ia->chan_desc1.h0.h) { + cd1.h = 1; + gsm48_decode_chan_h1(&ia->chan_desc1, &cd1.tsc, &cd1.maio, + &cd1.hsn); + LOGP(DRR, LOGL_INFO, " assignment 1 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ia->timing_advance1, + ia->timing_advance1 * GSM_TA_CM / 100, + ia->req_ref1.ra, ia->chan_desc1.chan_nr, cd1.maio, + cd1.hsn, ch_ts, ch_subch, cd1.tsc); + } else { + cd1.h = 0; + gsm48_decode_chan_h0(&ia->chan_desc1, &cd1.tsc, &cd1.arfcn); + LOGP(DRR, LOGL_INFO, " assignment 1 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x ARFCN %u TS %u SS %u TSC %u)\n", + ia->timing_advance1, + ia->timing_advance1 * GSM_TA_CM / 100, + ia->req_ref1.ra, ia->chan_desc1.chan_nr, cd1.arfcn, + ch_ts, ch_subch, cd1.tsc); + } + cd2.chan_nr = ia->chan_desc2.chan_nr; + rsl_dec_chan_nr(cd2.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ia->chan_desc2.h0.h) { + cd2.h = 1; + gsm48_decode_chan_h1(&ia->chan_desc2, &cd2.tsc, &cd2.maio, + &cd2.hsn); + LOGP(DRR, LOGL_INFO, " assignment 2 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ia->timing_advance2, + ia->timing_advance2 * GSM_TA_CM / 100, + ia->req_ref2.ra, ia->chan_desc2.chan_nr, cd2.maio, + cd2.hsn, ch_ts, ch_subch, cd2.tsc); + } else { + cd2.h = 0; + gsm48_decode_chan_h0(&ia->chan_desc2, &cd2.tsc, &cd2.arfcn); + LOGP(DRR, LOGL_INFO, " assignment 2 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x ARFCN %u TS %u SS %u TSC %u)\n", + ia->timing_advance2, + ia->timing_advance2 * GSM_TA_CM / 100, + ia->req_ref2.ra, ia->chan_desc2.chan_nr, cd2.arfcn, + ch_ts, ch_subch, cd2.tsc); + } + + /* 3.3.1.1.2: ignore assignment while idle */ + if (rr->state != GSM48_RR_ST_CONN_PEND || rr->wait_assign == 0) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + if (rr->wait_assign == 2) { + LOGP(DRR, LOGL_INFO, "Ignoring, channel already assigned.\n"); + return 0; + } + + /* request ref 1 */ + if (gsm48_match_ra(ms, &ia->req_ref1)) { + /* channel description */ + memcpy(&rr->cd_now, &cd1, sizeof(rr->cd_now)); + /* timing advance */ + rr->cd_now.ta = ia->timing_advance1; + /* mobile allocation */ + memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len, + ia->mob_alloc_len + 1); + rr->wait_assign = 2; + return gsm48_rr_dl_est(ms); + } + /* request ref 1 */ + if (gsm48_match_ra(ms, &ia->req_ref2)) { + /* channel description */ + memcpy(&rr->cd_now, &cd2, sizeof(rr->cd_now)); + /* timing advance */ + rr->cd_now.ta = ia->timing_advance2; + /* mobile allocation */ + memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len, + ia->mob_alloc_len + 1); + rr->wait_assign = 2; + return gsm48_rr_dl_est(ms); + } + LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); + + return 0; +} + +/* 9.1.20 IMMEDIATE ASSIGNMENT REJECT is received */ +static int gsm48_rr_rx_imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_imm_ass_rej *ia = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*ia); + int i; + struct gsm48_req_ref *req_ref; + uint8_t t3122_value; + + /* 3.3.1.1.2: ignore assignment while idle */ + if (rr->state != GSM48_RR_ST_CONN_PEND || rr->wait_assign == 0) + return 0; + + if (rr->wait_assign == 2) { + return 0; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT " + "REJECT message.\n"); + return -EINVAL; + } + + 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 (gsm48_match_ra(ms, req_ref)) { + /* wait indication */ + t3122_value = *(((uint8_t *)&ia->wait_ind1) + i * 4); + if (t3122_value) + start_rr_t3122(rr, t3122_value, 0); + /* start timer 3126 if not already */ + if (!bsc_timer_pending(&rr->t3126)) + start_rr_t3126(rr, 5, 0); /* TODO improve! */ + /* stop assignmnet requests */ + rr->n_chan_req = 0; + + /* wait until timer 3126 expires, then release + * or wait for channel assignment */ + return 0; + } + } + + return 0; +} + +/* 9.1.1 ADDITIONAL ASSIGMENT is received */ +static int gsm48_rr_rx_add_ass(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_add_ass *aa = (struct gsm48_add_ass *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*aa); + struct tlv_parsed tp; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of ADDITIONAL ASSIGNMENT " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, aa->data, payload_len, 0, 0); + + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); +} + +/* + * measturement reports + */ + +static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_rr_meas *meas = &rr->meas; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_meas_res *mr; + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + mr = (struct gsm48_meas_res *) msgb_put(nmsg, sizeof(*mr)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_MEAS_REP; + + /* measurement results */ + mr->rxlev_full = meas->rxlev_full; + mr->rxlev_sub = meas->rxlev_sub; + mr->rxqual_full = meas->rxqual_full; + mr->rxqual_sub = meas->rxqual_sub; + mr->dtx_used = meas->dtx; + mr->ba_used = meas->ba; + mr->meas_valid = meas->meas_valid; + if (meas->ncell_na) { + /* no results for serving cells */ + mr->no_nc_n_hi = 1; + mr->no_nc_n_lo = 3; + } else { + mr->no_nc_n_hi = meas->count >> 2; + mr->no_nc_n_lo = meas->count & 3; + } + mr->rxlev_nc1 = meas->rxlev_nc[0]; + mr->rxlev_nc2_hi = meas->rxlev_nc[1] >> 1; + mr->rxlev_nc2_lo = meas->rxlev_nc[1] & 1; + mr->rxlev_nc3_hi = meas->rxlev_nc[2] >> 2; + mr->rxlev_nc3_lo = meas->rxlev_nc[2] & 3; + mr->rxlev_nc4_hi = meas->rxlev_nc[3] >> 3; + mr->rxlev_nc4_lo = meas->rxlev_nc[3] & 7; + mr->rxlev_nc5_hi = meas->rxlev_nc[4] >> 4; + mr->rxlev_nc5_lo = meas->rxlev_nc[4] & 15; + mr->rxlev_nc6_hi = meas->rxlev_nc[5] >> 5; + mr->rxlev_nc6_lo = meas->rxlev_nc[5] & 31; + mr->bsic_nc1_hi = meas->bsic_nc[0] >> 3; + mr->bsic_nc1_lo = meas->bsic_nc[0] & 7; + mr->bsic_nc2_hi = meas->bsic_nc[1] >> 4; + mr->bsic_nc2_lo = meas->bsic_nc[1] & 15; + mr->bsic_nc3_hi = meas->bsic_nc[2] >> 5; + mr->bsic_nc3_lo = meas->bsic_nc[2] & 31; + mr->bsic_nc4 = meas->bsic_nc[3]; + mr->bsic_nc5 = meas->bsic_nc[4]; + mr->bsic_nc6 = meas->bsic_nc[5]; + mr->bcch_f_nc1 = meas->bcch_f_nc[0]; + mr->bcch_f_nc2 = meas->bcch_f_nc[1]; + mr->bcch_f_nc3 = meas->bcch_f_nc[2]; + mr->bcch_f_nc4 = meas->bcch_f_nc[3]; + mr->bcch_f_nc5_hi = meas->bcch_f_nc[4] >> 1; + mr->bcch_f_nc5_lo = meas->bcch_f_nc[4] & 1; + mr->bcch_f_nc6_hi = meas->bcch_f_nc[5] >> 2; + mr->bcch_f_nc6_lo = meas->bcch_f_nc[5] & 3; + + return gsm48_send_rsl(ms, RSL_MT_UNIT_DATA_REQ, nmsg); +} + +/* + * link establishment and release + */ + +/* process "Loss Of Signal" */ +int gsm48_rr_los(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t *mode; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + LOGP(DSUM, LOGL_INFO, "Radio link lost signal\n"); + + /* stop T3211 if running */ + stop_rr_t3110(rr); + + switch(rr->state) { + case GSM48_RR_ST_CONN_PEND: + LOGP(DRR, LOGL_INFO, "LOS during RACH request\n"); + + /* stop pending RACH timer */ + stop_rr_t3126(rr); + break; + case GSM48_RR_ST_DEDICATED: + LOGP(DRR, LOGL_INFO, "LOS during dedicated mode, release " + "locally\n"); + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* release message */ + rel_local: + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 1; /* local release */ + /* start release */ + return gsm48_send_rsl_rel(ms, RSL_MT_REL_REQ, nmsg); + case GSM48_RR_ST_REL_PEND: + LOGP(DRR, LOGL_INFO, "LOS during RR release procedure, release " + "locally\n"); + + /* stop pending RACH timer */ + stop_rr_t3110(rr); + + /* release locally */ + goto rel_local; + default: + /* this should not happen */ + LOGP(DRR, LOGL_ERROR, "LOS in IDLE state, ignoring\n"); + return -EINVAL; + } + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_LOST_SIGNAL; + gsm48_rr_upmsg(ms, nmsg); + + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + return 0; +} + +/* activate link and send establish request */ +static int gsm48_rr_dl_est(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_pag_rsp *pr; + uint8_t mi[11]; + uint8_t ch_type, ch_subch, ch_ts; + uint16_t ma[64]; + uint8_t ma_len; + + if (rr->cd_now.h) { + gsm48_decode_mobile_alloc(s, rr->cd_now.mob_alloc_lv + 1, + rr->cd_now.mob_alloc_lv[0], ma, &ma_len, 0); + if (ma_len < 1) { + LOGP(DRR, LOGL_NOTICE, "Hopping, but no allocation\n"); + return -EINVAL; + } + } + + /* 3.3.1.1.3.1 */ + stop_rr_t3126(rr); + + /* send DL_EST_REQ */ + if (rr->rr_est_msg) { + /* use queued message */ + nmsg = rr->rr_est_msg; + rr->rr_est_msg = 0; + LOGP(DRR, LOGL_INFO, "sending establish message\n"); + } else { + /* create paging response */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_PAG_RESP; + pr = (struct gsm48_pag_rsp *) msgb_put(nmsg, sizeof(*pr)); + /* key sequence */ + pr->key_seq = subscr->key_seq; + /* classmark 2 */ + pr->cm2_len = sizeof(pr->cm2); + gsm48_rr_enc_cm2(ms, &pr->cm2); + /* mobile identity */ + if (ms->subscr.tmsi_valid) { + gsm48_generate_mid_from_tmsi(mi, subscr->tmsi); + LOGP(DRR, LOGL_INFO, "sending paging response with " + "TMSI\n"); + } else if (subscr->imsi[0]) { + gsm48_generate_mid_from_imsi(mi, subscr->imsi); + LOGP(DRR, LOGL_INFO, "sending paging response with " + "IMSI\n"); + } else { + mi[1] = 1; + mi[2] = 0xf0 | GSM_MI_TYPE_NONE; + LOGP(DRR, LOGL_INFO, "sending paging response without " + "TMSI/IMSI\n"); + } + msgb_put(nmsg, 1 + mi[1]); + memcpy(pr->data, mi + 1, 1 + mi[1]); + } + + /* reset scheduler */ + LOGP(DRR, LOGL_INFO, "resetting scheduler\n"); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_SCHED); + + /* setting (new) timing advance */ + rr->ind_ta = rr->cd_now.ta; + LOGP(DRR, LOGL_INFO, "setting indicated TA %d (actual TA %d)\n", + rr->ind_ta, rr->ind_ta - set->alter_delay); + l1ctl_tx_ph_param_req(ms, rr->ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : rr->ind_tx_power); + + /* activate channel */ + LOGP(DRR, LOGL_INFO, "activating channel\n"); +#ifdef TODO + RSL_MT_ to activate channel with all the cd_now informations +#else + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); + if ((ch_type != RSL_CHAN_SDCCH8_ACCH + && ch_type != RSL_CHAN_SDCCH4_ACCH) || ch_ts > 4 || ch_subch >= 4) { + printf("Channel type %d, subch %d, ts %d not supported, " + "exitting.\n", ch_type, ch_subch, ch_ts); + exit(-ENOTSUP); + } + if (rr->cd_now.h) + tx_ph_dm_est_req_h1(ms, rr->cd_now.maio, rr->cd_now.hsn, + ma, ma_len, rr->cd_now.chan_nr, rr->cd_now.tsc); + else + tx_ph_dm_est_req_h0(ms, rr->cd_now.arfcn, rr->cd_now.chan_nr, + rr->cd_now.tsc); +#endif + + /* start establishmnet */ + return gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg); +} + +/* the link is established */ +static int gsm48_rr_estab_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t *mode; + struct msgb *nmsg; + + /* if MM has releases before confirm, we start release */ + if (rr->state == GSM48_RR_ST_REL_PEND) { + LOGP(DRR, LOGL_INFO, "MM already released RR.\n"); + /* release message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 0; /* normal release */ + /* start release */ + return gsm48_send_rsl_rel(ms, RSL_MT_REL_REQ, nmsg); + } + + /* 3.3.1.1.4 */ + new_rr_state(rr, GSM48_RR_ST_DEDICATED); + + /* send confirm to upper layer */ + nmsg = gsm48_rr_msgb_alloc( + (rr->rr_est_req) ? GSM48_RR_EST_CNF : GSM48_RR_EST_IND); + if (!nmsg) + return -ENOMEM; + return gsm48_rr_upmsg(ms, nmsg); +} + +/* the link is released in pending state (by l2) */ +static int gsm48_rr_rel_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + LOGP(DSUM, LOGL_INFO, "Radio link is released\n"); + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_NORMAL; + gsm48_rr_upmsg(ms, nmsg); + + /* start release timer, so UA will be transmitted */ + start_rr_t_rel_wait(rr, 1, 500000); + + /* pending release */ + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + return 0; +} + +/* 9.1.7 CHANNEL RELEASE is received */ +static int gsm48_rr_rx_chan_rel(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_rel *cr = (struct gsm48_chan_rel *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cr); + struct tlv_parsed tp; + struct msgb *nmsg; + uint8_t *mode; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL RELEASE " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, cr->data, payload_len, 0, 0); + + LOGP(DRR, LOGL_INFO, "channel release request with cause 0x%02x)\n", + cr->rr_cause); + + /* BA range */ + if (TLVP_PRESENT(&tp, GSM48_IE_BA_RANGE)) { + gsm48_decode_ba_range(TLVP_VAL(&tp, GSM48_IE_BA_RANGE), + *(TLVP_VAL(&tp, GSM48_IE_BA_RANGE) - 1), rr->ba_range, + &rr->ba_ranges, + sizeof(rr->ba_range) / sizeof(rr->ba_range[0])); + /* NOTE: the ranges are kept until IDLE state is returned + * (see new_rr_state) + */ + } + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* start T3110, so that two DISCs can be sent due to T200 timeout */ + start_rr_t3110(rr, 1, 500000); + + /* disconnect the main signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 0; /* normal release */ + return gsm48_send_rsl_rel(ms, RSL_MT_REL_REQ, nmsg); +} + +/* + * chanel mode modify, assignment, and handover + */ + +/* 9.1.6 sending CHANNEL MODE MODIFY ACKNOWLEDGE */ +static int gsm48_rr_tx_chan_modify_ack(struct osmocom_ms *ms, + struct gsm48_chan_desc *cd, uint8_t mode) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_chan_mode_modify *cm; + + LOGP(DRR, LOGL_INFO, "CHAN.MODE.MOD ACKNOWLEDGE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + cm = (struct gsm48_chan_mode_modify *) msgb_put(nmsg, sizeof(*cm)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF_ACK; + + /* CD */ + memcpy(&cm->chan_desc, cd, sizeof(struct gsm48_chan_desc)); + /* mode */ + cm->mode = mode; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg); +} + +/* 9.1.5 CHANNEL MODE MODIFY is received */ +static int gsm48_rr_rx_chan_modify(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_mode_modify *cm = + (struct gsm48_chan_mode_modify *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cm); + struct gsm48_rr_cd *cd = &rr->cd_now; + uint8_t ch_type, ch_subch, ch_ts; + uint8_t mode; + + LOGP(DRR, LOGL_INFO, "CHANNEL MODE MODIFY\n"); + + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL MODE MODIFY " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* decode channel description */ + cd->chan_nr = cm->chan_desc.chan_nr; + rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (cm->chan_desc.h0.h) { + cd->h = 1; + gsm48_decode_chan_h1(&cm->chan_desc, &cd->tsc, &cd->maio, + &cd->hsn); + LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x MAIO %u HSN %u TS %u " + "SS %u TSC %u mode %u)\n", cm->chan_desc.chan_nr, + cd->maio, cd->hsn, ch_ts, ch_subch, cd->tsc, cm->mode); + } else { + cd->h = 0; + gsm48_decode_chan_h0(&cm->chan_desc, &cd->tsc, &cd->arfcn); + LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x ARFCN %u TS %u SS %u " + "TSC %u mode %u)\n", cm->chan_desc.chan_nr, cd->arfcn, + ch_ts, ch_subch, cd->tsc, cm->mode); + } + /* mode */ + mode = cm->mode; + switch (mode) { + case GSM48_CMODE_SIGN: + LOGP(DRR, LOGL_INFO, "Mode set to signalling.\n"); + break; + case GSM48_CMODE_SPEECH_V1: + LOGP(DRR, LOGL_INFO, "Mode set to GSM full-rate codec.\n"); + break; + default: + LOGP(DRR, LOGL_ERROR, "Mode %u not supported!\n", mode); + } + + return gsm48_rr_tx_chan_modify_ack(ms, &cm->chan_desc, mode); +} + +/* 9.1.3 sending ASSIGNMENT COMPLETE */ +static int gsm48_rr_tx_ass_cpl(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_ass_cpl *ac; + + LOGP(DRR, LOGL_INFO, "ASSIGNMENT COMPLETE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + ac = (struct gsm48_ass_cpl *) msgb_put(nmsg, sizeof(*ac)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_COMPL; + + /* RR_CAUSE */ + ac->rr_cause = cause; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg); +} + +/* 9.1.4 sending ASSIGNMENT FAILURE */ +static int gsm48_rr_tx_ass_fail(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_ass_fail *af; + + LOGP(DRR, LOGL_INFO, "ASSIGNMENT FAILURE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + af = (struct gsm48_ass_fail *) msgb_put(nmsg, sizeof(*af)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_COMPL; + + /* RR_CAUSE */ + af->rr_cause = cause; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg); +} + +/* 9.1.2 ASSIGNMENT COMMAND is received */ +static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg) +{ +// struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_ass_cmd *ac = (struct gsm48_ass_cmd *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*ac); + struct tlv_parsed tp; + struct gsm48_rr_cd cd; + + LOGP(DRR, LOGL_INFO, "ASSIGNMENT COMMAND\n"); + + memset(&cd, 0, sizeof(cd)); + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of ASSIGNMENT COMMAND " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, ac->data, payload_len, 0, 0); + +#if 0 + /* channel description */ + memcpy(&cd.chan_desc, &ac->chan_desc, sizeof(chan_desc)); + /* power command */ + cd.power_command = ac->power_command; + /* frequency list, after timer */ + tlv_copy(&cd.fl, sizeof(fl_after), &tp, GSM48_IE_FRQLIST_AFTER); + /* cell channel description */ + tlv_copy(&cd.ccd, sizeof(ccd), &tp, GSM48_IE_CELL_CH_DESC); + /* multislot allocation */ + tlv_copy(&cd.multia, sizeof(ma), &tp, GSM48_IE_MSLOT_DESC); + /* channel mode */ + tlv_copy(&cd.chanmode, sizeof(chanmode), &tp, GSM48_IE_CHANMODE_1); + /* mobile allocation, after time */ + tlv_copy(&cd.moba_after, sizeof(moba_after), &tp, GSM48_IE_MOB_AL_AFTER); + /* starting time */ + tlv_copy(&cd.start, sizeof(start), &tp, GSM_IE_START_TIME); + /* frequency list, before time */ + tlv_copy(&cd.fl_before, sizeof(fl_before), &tp, GSM48_IE_FRQLIST_BEFORE); + /* channel description, before time */ + tlv_copy(&cd.chan_desc_before, sizeof(cd_before), &tp, GSM48_IE_CHDES_1_BEFORE); + /* frequency channel sequence, before time */ + tlv_copy(&cd.fcs_before, sizeof(fcs_before), &tp, GSM48_IE_FRQSEQ_BEFORE); + /* mobile allocation, before time */ + tlv_copy(&cd.moba_before, sizeof(moba_before), &tp, GSM48_IE_MOB_AL_BEFORE); + /* cipher mode setting */ + if (TLVP_PRESENT(&tp, GSM48_IE_CIP_MODE_SET)) + cd.cipher = *TLVP_VAL(&tp, GSM48_IE_CIP_MODE_SET); + else + cd.cipher = 0; + + if (no CA) { + LOGP(DRR, LOGL_INFO, "No current cell allocation available.\n"); + return gsm48_rr_tx_ass_fail(ms, GSM48_GSM48_RR_CAUSE_NO_CELL_ALLOC_A); + } + + if (not supported) { + LOGP(DRR, LOGL_INFO, "New channel is not supported.\n"); + return gsm48_rr_tx_ass_fail(ms, GSM48_RR_CAUSE_CHAN_MODE_UNACCEPT); + } + + if (freq not supported) { + LOGP(DRR, LOGL_INFO, "New frequency is not supported.\n"); + return gsm48_rr_tx_ass_fail(ms, GSM48_RR_CAUSE_FREQ_NOT_IMPL); + } + + /* store current channel descriptions, to return in case of failure */ + memcpy(&rr->chan_last, &rr->chan_desc, sizeof(*cd)); + /* copy new description */ + memcpy(&rr->chan_desc, cd, sizeof(cd)); + + /* start suspension of current link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gsm48_send_rsl(ms, RSL_MT_SUSP_REQ, msg); + + /* change into special assignment suspension state */ + rr->assign_susp_state = 1; + rr->resume_last_state = 0; +#else + return gsm48_rr_tx_ass_fail(ms, GSM48_RR_CAUSE_FREQ_NOT_IMPL); +#endif + + return 0; +} + +/* + * radio ressource requests + */ + +/* establish request for dedicated mode */ +static int gsm48_rr_est_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t cause; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint16_t acc_class; + + /* 3.3.1.1.3.2 */ + if (bsc_timer_pending(&rr->t3122)) { + if (rrh->cause != RR_EST_CAUSE_EMERGENCY) { + LOGP(DRR, LOGL_INFO, "T3122 running, rejecting!\n"); + cause = RR_REL_CAUSE_T3122; + reject: + LOGP(DSUM, LOGL_INFO, "Establishing radio link not " + "possible\n"); + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = cause; + return gsm48_rr_upmsg(ms, nmsg); + } + LOGP(DRR, LOGL_INFO, "T3122 running, but emergency call\n"); + stop_rr_t3122(rr); + } + + /* if state is not idle */ + if (rr->state != GSM48_RR_ST_IDLE) { + LOGP(DRR, LOGL_INFO, "We are not IDLE yet, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* cell selected */ + if (!cs->selected) { + LOGP(DRR, LOGL_INFO, "No cell selected, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* check if camping */ + if (cs->state != GSM322_C3_CAMPED_NORMALLY + && rrh->cause != RR_EST_CAUSE_EMERGENCY) { + LOGP(DRR, LOGL_INFO, "Not camping normally, rejecting!\n"); + cause = RR_REL_CAUSE_EMERGENCY_ONLY; + goto reject; + } + if (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) { + LOGP(DRR, LOGL_INFO, "Not camping, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* check for relevant informations */ + if (!s->si3) { + LOGP(DRR, LOGL_INFO, "Not enough SI, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* 3.3.1.1.1 */ + if (!subscr->acc_barr && s->cell_barr) { + LOGP(DRR, LOGL_INFO, "Cell barred, rejecting!\n"); + cause = RR_REL_CAUSE_NOT_AUTHORIZED; + goto reject; + } + if (rrh->cause == RR_EST_CAUSE_EMERGENCY) + acc_class = subscr->acc_class | 0x0400; + else + acc_class = subscr->acc_class & 0xfbff; + if (!subscr->acc_barr && !(acc_class & (s->class_barr ^ 0xffff))) { + LOGP(DRR, LOGL_INFO, "Cell barred for our access class (access " + "%04x barred %04x)!\n", acc_class, s->class_barr); + cause = RR_REL_CAUSE_NOT_AUTHORIZED; + goto reject; + } + + /* requested by RR */ + rr->rr_est_req = 1; + + /* clone and store REQUEST message */ + if (!gh) { + LOGP(DRR, LOGL_ERROR, "Error, missing l3 message\n"); + return -EINVAL; + } + rr->rr_est_msg = gsm48_l3_msgb_alloc(); + if (!rr->rr_est_msg) + return -ENOMEM; + memcpy(msgb_put(rr->rr_est_msg, msgb_l3len(msg)), + msgb_l3(msg), msgb_l3len(msg)); + + /* request channel */ + return gsm48_rr_chan_req(ms, rrh->cause, 0); +} + +/* send all queued messages down to layer 2 */ +static int gsm48_rr_dequeue_down(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *msg; + + while((msg = msgb_dequeue(&rr->downqueue))) { + LOGP(DRR, LOGL_INFO, "Sending queued message.\n"); + gsm48_send_rsl(ms, RSL_MT_DATA_REQ, msg); + } + + return 0; +} + +/* 3.4.2 transfer data in dedicated mode */ +static int gsm48_rr_data_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (rr->state != GSM48_RR_ST_DEDICATED) { + msgb_free(msg); + return -EINVAL; + } + + /* pull RR header */ + msgb_pull(msg, sizeof(struct gsm48_rr_hdr)); + + /* queue message, during handover or assignment procedure */ + if (rr->hando_susp_state || rr->assign_susp_state) { + LOGP(DRR, LOGL_INFO, "Queueing message during suspend.\n"); + msgb_enqueue(&rr->downqueue, msg); + return 0; + } + + /* forward message */ + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, msg); +} + +/* + * data indications from data link + */ + +/* 3.4.2 data from layer 2 to RR and upper layer*/ +static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_rr_hdr *rrh; + uint8_t pdisc = gh->proto_discr & 0x0f; + + if (pdisc == GSM48_PDISC_RR) { + int rc = -EINVAL; + uint8_t skip_ind = (gh->proto_discr & 0xf0) >> 4; + + /* ignore if skip indicator is not B'0000' */ + if (skip_ind) + return 0; + + switch(gh->msg_type) { + case GSM48_MT_RR_ADD_ASS: + rc = gsm48_rr_rx_add_ass(ms, msg); + break; + case GSM48_MT_RR_ASS_CMD: + rc = gsm48_rr_rx_ass_cmd(ms, msg); + break; +#if 0 + case GSM48_MT_RR_CIP_MODE_CMD: + rc = gsm48_rr_rx_cip_mode_cmd(ms, msg); + break; +#endif + case GSM48_MT_RR_CLSM_ENQ: + rc = gsm48_rr_rx_cm_enq(ms, msg); + break; + case GSM48_MT_RR_CHAN_MODE_MODIF: + rc = gsm48_rr_rx_chan_modify(ms, msg); + break; +#if 0 + case GSM48_MT_RR_HANDO_CMD: + rc = gsm48_rr_rx_hando_cmd(ms, msg); + break; + case GSM48_MT_RR_FREQ_REDEF: + rc = gsm48_rr_rx_freq_redef(ms, msg); + break; +#endif + case GSM48_MT_RR_CHAN_REL: + rc = gsm48_rr_rx_chan_rel(ms, msg); + break; + default: + LOGP(DRR, LOGL_NOTICE, "Message type 0x%02x unknown.\n", + gh->msg_type); + + /* status message */ + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N); + } + + msgb_free(msg); + return rc; + } + + /* pull off RSL header up to L3 message */ + msgb_pull(msg, (long)msgb_l3(msg) - (long)msg->data); + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_rr_hdr)); + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->msg_type = GSM48_RR_DATA_IND; + + return gsm48_rr_upmsg(ms, msg); +} + +/* receive BCCH at RR layer */ +static int gsm48_rr_rx_bcch(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_SYSINFO_1: + return gsm48_rr_rx_sysinfo1(ms, msg); + case GSM48_MT_RR_SYSINFO_2: + return gsm48_rr_rx_sysinfo2(ms, msg); + case GSM48_MT_RR_SYSINFO_2bis: + return gsm48_rr_rx_sysinfo2bis(ms, msg); + case GSM48_MT_RR_SYSINFO_2ter: + return gsm48_rr_rx_sysinfo2ter(ms, msg); + case GSM48_MT_RR_SYSINFO_3: + return gsm48_rr_rx_sysinfo3(ms, msg); + case GSM48_MT_RR_SYSINFO_4: + return gsm48_rr_rx_sysinfo4(ms, msg); + default: +#if 0 + LOGP(DRR, LOGL_NOTICE, "BCCH message type 0x%02x not sup.\n", + sih->system_information); +#endif + return -EINVAL; + } +} + +/* receive CCCH at RR layer */ +static int gsm48_rr_rx_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: + return gsm48_rr_rx_pag_req_1(ms, msg); + case GSM48_MT_RR_PAG_REQ_2: + return gsm48_rr_rx_pag_req_2(ms, msg); + case GSM48_MT_RR_PAG_REQ_3: + return gsm48_rr_rx_pag_req_3(ms, msg); + + case GSM48_MT_RR_IMM_ASS: + return gsm48_rr_rx_imm_ass(ms, msg); + case GSM48_MT_RR_IMM_ASS_EXT: + return gsm48_rr_rx_imm_ass_ext(ms, msg); + case GSM48_MT_RR_IMM_ASS_REJ: + return gsm48_rr_rx_imm_ass_rej(ms, msg); + default: +#if 0 + LOGP(DRR, LOGL_NOTICE, "CCCH message type 0x%02x unknown.\n", + sih->system_information); +#endif + return -EINVAL; + } +} + +/* receive ACCH at RR layer */ +static int gsm48_rr_rx_acch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_settings *set = &ms->settings; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + uint8_t ind_ta, ind_tx_power; + + if (msgb_l2len(msg) < sizeof(*rllh) + 2 + 2) { + LOGP(DRR, LOGL_ERROR, "Missing TA and TX_POWER IEs\n"); + return -EINVAL; + } + + ind_ta = rllh->data[1]; + ind_tx_power = rllh->data[3]; + LOGP(DRR, LOGL_INFO, "Indicated ta %d (actual ta %d)\n", + ind_ta, ind_ta - set->alter_delay); + LOGP(DRR, LOGL_INFO, "Indicated tx_power %d\n", + ind_tx_power); + if (ind_ta != rr->ind_ta || ind_tx_power != rr->ind_tx_power) { + LOGP(DRR, LOGL_INFO, "setting new ta and tx_power\n"); + l1ctl_tx_ph_param_req(ms, ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : ind_tx_power); + rr->ind_ta = ind_ta; + rr->ind_tx_power = ind_tx_power; + } + + switch (sih->system_information) { + case GSM48_MT_RR_SYSINFO_5: + return gsm48_rr_rx_sysinfo5(ms, msg); + case GSM48_MT_RR_SYSINFO_5bis: + return gsm48_rr_rx_sysinfo5bis(ms, msg); + case GSM48_MT_RR_SYSINFO_5ter: + return gsm48_rr_rx_sysinfo5ter(ms, msg); + case GSM48_MT_RR_SYSINFO_6: + return gsm48_rr_rx_sysinfo6(ms, msg); + default: + LOGP(DRR, LOGL_NOTICE, "ACCH message type 0x%02x unknown.\n", + sih->system_information); + return -EINVAL; + } +} + +/* unit data from layer 2 to RR layer */ +static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct tlv_parsed tv; + uint8_t ch_type, ch_subch, ch_ts; + + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", + rllh->chan_nr, rllh->link_id); + + rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { + DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); + return -EIO; + } + msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); + + if (cs->ccch_state != GSM322_CCCH_ST_SYNC + && cs->ccch_state != GSM322_CCCH_ST_DATA) + return -EINVAL; + + /* when camping, start/reset loss timer */ + if (cs->state == GSM322_C3_CAMPED_NORMALLY + || cs->state == GSM322_C7_CAMPED_ANY_CELL) { + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; +#ifdef TODO + set radio link timeout on layer 1 + it is the number of subsequent BCCH blocks. (about 1/4 seconds) +#else + /* use maximu loss timer, if to value is not available yet */ + start_loss_timer(cs, ((rr->state == GSM48_RR_ST_DEDICATED) + ? ((s->sacch_radio_link_timeout) ? : 64) + : s->bcch_radio_link_timeout) / 4, 0); +#endif + } + + /* temporary moved here until confirm is fixed */ + if (cs->ccch_state != GSM322_CCCH_ST_DATA) { + LOGP(DCS, LOGL_INFO, "Channel provides data.\n"); + cs->ccch_state = GSM322_CCCH_ST_DATA; + + /* in dedicated mode */ + if (ms->rrlayer.state == GSM48_RR_ST_CONN_PEND) + return gsm48_rr_tx_rand_acc(ms, NULL); + + /* set timer for reading BCCH */ + if (cs->state == GSM322_C2_STORED_CELL_SEL + || cs->state == GSM322_C1_NORMAL_CELL_SEL + || cs->state == GSM322_C6_ANY_CELL_SEL + || cs->state == GSM322_C4_NORMAL_CELL_RESEL + || cs->state == GSM322_C8_ANY_CELL_RESEL + || cs->state == GSM322_C5_CHOOSE_CELL + || cs->state == GSM322_C9_CHOOSE_ANY_CELL + || cs->state == GSM322_PLMN_SEARCH + || cs->state == GSM322_HPLMN_SEARCH) + start_cs_timer(cs, ms->support.scan_to, 0); + // TODO: timer depends on BCCH config + } + + rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + switch (ch_type) { + case RSL_CHAN_PCH_AGCH: + return gsm48_rr_rx_pch_agch(ms, msg); + case RSL_CHAN_BCCH: + return gsm48_rr_rx_bcch(ms, msg); + case RSL_CHAN_SDCCH4_ACCH: + return gsm48_rr_rx_acch(ms, msg); + case RSL_CHAN_SDCCH8_ACCH: + return gsm48_rr_rx_acch(ms, msg); + default: + LOGP(DRSL, LOGL_NOTICE, "RSL with chan_nr 0x%02x unknown.\n", + rllh->chan_nr); + return -EINVAL; + } +} + +/* 3.4.13.3 RR abort in dedicated mode (also in conn. pending mode) */ +static int gsm48_rr_abort_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t *mode; + + /* stop pending RACH timer */ + stop_rr_t3126(rr); + + /* release "normally" if we are in dedicated mode */ + if (rr->state == GSM48_RR_ST_DEDICATED) { + struct msgb *nmsg; + + LOGP(DRR, LOGL_INFO, "Abort in dedicated state, send release " + "to layer 2.\n"); + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* release message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 0; /* normal release */ + return gsm48_send_rsl_rel(ms, RSL_MT_REL_REQ, nmsg); + } + + LOGP(DRR, LOGL_INFO, "Abort in connection pending state, return to " + "idle state.\n"); + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + + return 0; +} + +/* release confirm in dedicated mode */ +static int gsm48_rr_susp_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (rr->hando_susp_state || rr->assign_susp_state) { + struct msgb *nmsg; + + /* change radio to new channel */ +//todo tx_ph_dm_est_req(ms, rr->cd_now.arfcn, rr->cd_now.chan_nr, +// rr->cd_now.tsc); + + /* send DL-ESTABLISH REQUEST */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg); + +#ifdef TODO + /* trigger RACH */ + if (rr->hando_susp_state) { + gsm48_rr_tx_hando_access(ms); + rr->hando_acc_left = 3; + } +#endif + } + return 0; +} + +/* release confirm */ +static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + LOGP(DSUM, LOGL_INFO, "Requesting channel aborted\n"); + + /* stop T3211 if running */ + stop_rr_t3110(rr); + + /* send release indication */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_NORMAL; + gsm48_rr_upmsg(ms, nmsg); + + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + return 0; +} + +/* + * state machines + */ + +/* state trasitions for link layer messages (lower layer) */ +static struct dldatastate { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} dldatastatelist[] = { + /* data transfer */ + {SBIT(GSM48_RR_ST_IDLE) | + SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED) | + SBIT(GSM48_RR_ST_REL_PEND), + RSL_MT_UNIT_DATA_IND, gsm48_rr_unit_data_ind}, + + {SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.2 */ + RSL_MT_DATA_IND, gsm48_rr_data_ind}, + + /* esablish */ + {SBIT(GSM48_RR_ST_IDLE) | + SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_REL_PEND), + RSL_MT_EST_CONF, gsm48_rr_estab_cnf}, + +#if 0 + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_EST_CONF, gsm48_rr_estab_cnf_dedicated}, + + {SBIT(GSM_RRSTATE), + RSL_MT_CONNECT_CNF, gsm48_rr_connect_cnf}, + +#endif + + /* release */ + {SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_REL_IND, gsm48_rr_rel_ind}, + + {SBIT(GSM48_RR_ST_REL_PEND), + RSL_MT_REL_CONF, gsm48_rr_rel_cnf}, + + /* suspenion */ + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_SUSP_CONF, gsm48_rr_susp_cnf_dedicated}, + +#if 0 + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_CHAN_CNF, gsm48_rr_rand_acc_cnf_dedicated}, + + {SBIT(GSM_RRSTATE), + RSL_MT_MDL_ERROR_IND, gsm48_rr_mdl_error_ind}, +#endif +}; + +#define DLDATASLLEN \ + (sizeof(dldatastatelist) / sizeof(struct dldatastate)) + +static int gsm48_rcv_rll(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int msg_type = rllh->c.msg_type; + int i; + int rc; + + if (msg_type != RSL_MT_UNIT_DATA_IND) { + LOGP(DRSL, LOGL_INFO, "(ms %s) Received '%s' from L2 in state " + "%s\n", ms->name, get_rsl_name(msg_type), + gsm48_rr_state_names[rr->state]); + } + + /* find function for current state and message */ + for (i = 0; i < DLDATASLLEN; i++) + if ((msg_type == dldatastatelist[i].type) + && ((1 << rr->state) & dldatastatelist[i].states)) + break; + if (i == DLDATASLLEN) { + LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); + msgb_free(msg); + return 0; + } + + rc = dldatastatelist[i].rout(ms, msg); + + /* free msgb unless it is forwarded */ + if (dldatastatelist[i].rout != gsm48_rr_data_ind) + msgb_free(msg); + + return rc; +} + +static int gsm48_rcv_cch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + int msg_type = ch->c.msg_type; + int rc; + + LOGP(DRSL, LOGL_INFO, "(ms %s) Received '%s' from L2 in state " + "%s\n", ms->name, get_rsl_name(msg_type), + gsm48_rr_state_names[rr->state]); + + if (rr->state == GSM48_RR_ST_CONN_PEND + && msg_type == RSL_MT_CHAN_CONF) { + rc = gsm48_rr_tx_rand_acc(ms, msg); + msgb_free(msg); + return rc; + } + + LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); + msgb_free(msg); + return 0; +} + + +/* input function for L2 messags up to L3 */ +static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc = 0; + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = gsm48_rcv_rll(ms, msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = gsm48_rcv_cch(ms, msg); + break; + default: + /* FIXME: implement this */ + LOGP(DRSL, LOGL_NOTICE, "unknown RSLms msg_discr 0x%02x\n", + rslh->msg_discr); + msgb_free(msg); + rc = -EINVAL; + break; + } + + return rc; +} + +/* state trasitions for RR-SAP messages from up */ +static struct rrdownstate { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} rrdownstatelist[] = { + /* NOTE: If not IDLE, it is rejected there. */ + {ALL_STATES, /* 3.3.1.1 */ + GSM48_RR_EST_REQ, gsm48_rr_est_req}, + + {SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.2 */ + GSM48_RR_DATA_REQ, gsm48_rr_data_req}, + + {SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.13.3 */ + GSM48_RR_ABORT_REQ, gsm48_rr_abort_req}, + +#if 0 + {SBIT(GSM48_RR_ST_DEDICATED), + GSM48_RR_ACT_REQ, gsm48_rr_act_req}, +#endif +}; + +#define RRDOWNSLLEN \ + (sizeof(rrdownstatelist) / sizeof(struct rrdownstate)) + +int gsm48_rr_downmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + int msg_type = rrh->msg_type; + int i; + int rc; + + LOGP(DRR, LOGL_INFO, "(ms %s) Message '%s' received in state %s\n", + ms->name, get_rr_name(msg_type), + gsm48_rr_state_names[rr->state]); + + /* find function for current state and message */ + for (i = 0; i < RRDOWNSLLEN; i++) + if ((msg_type == rrdownstatelist[i].type) + && ((1 << rr->state) & rrdownstatelist[i].states)) + break; + if (i == RRDOWNSLLEN) { + LOGP(DRR, LOGL_NOTICE, "Message unhandled at this state.\n"); + msgb_free(msg); + return 0; + } + + rc = rrdownstatelist[i].rout(ms, msg); + + /* free msgb uless it is forwarded */ + if (rrdownstatelist[i].rout != gsm48_rr_data_req) + msgb_free(msg); + + return rc; +} + +/* + * init/exit + */ + +int gsm48_rr_init(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + memset(rr, 0, sizeof(*rr)); + rr->ms = ms; + + LOGP(DRR, LOGL_INFO, "init Radio Ressource process\n"); + + INIT_LLIST_HEAD(&rr->rsl_upqueue); + INIT_LLIST_HEAD(&rr->downqueue); + /* downqueue is handled here, so don't add_work */ + + osmol2_register_handler(ms, &gsm48_rx_rsl); + + return 0; +} + +int gsm48_rr_exit(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *msg; + + LOGP(DRR, LOGL_INFO, "exit Radio Ressource process\n"); + + /* flush queues */ + while ((msg = msgb_dequeue(&rr->rsl_upqueue))) + msgb_free(msg); + while ((msg = msgb_dequeue(&rr->downqueue))) + msgb_free(msg); + + if (rr->rr_est_msg) { + msgb_free(rr->rr_est_msg); + rr->rr_est_msg = NULL; + } + + stop_rr_t_monitor(rr); + stop_rr_t_rel_wait(rr); + stop_rr_t3110(rr); + stop_rr_t3122(rr); + stop_rr_t3126(rr); + + return 0; +} + +#if 0 + +the process above is complete +------------------------------------------------------------------------------ +incomplete + + + + + + + + + + + + + + + + + + + +todo: + +stop timers on abort +wird beim abbruch immer der gepufferte cm-service-request entfernt, auch beim verschicken?: +measurement reports +todo rr_sync_ind when receiving ciph, re ass, channel mode modify + +todo change procedures, release procedure + +static int gsm48_rr_act_req(struct osmocom_ms *ms, struct gsm48_rr *rrmsg) +{ +} + + +} + +/* memcopy of LV of given IE from tlv_parsed structure */ +static int tlv_copy(void *dest, int dest_len, struct tlv_parsed *tp, uint8_t ie) +{ + uint8_t *lv = dest; + uint8_t len; + + if (dest_len < 1) + return -EINVAL; + lv[0] = 0; + + if (!TLVP_PRESENT(tp, ie)) + return 0; + + len = TLVP_LEN(tp, ie); + if (len < 1) + return 0; + if (len + 1 > dest_len) + return -ENOMEM; + + memcpy(dest, TLVP_VAL(tp, ie) - 1, len + 1); + return 0; +} + + +/* decode "Cell Description" (10.5.2.2) */ +static int gsm48_decode_cell_desc(struct gsm48_cell_desc *cd, uint16_t *arfcn, uint8_t *ncc uint8_t *bcc) +{ + *arfcn = (cd->bcch_hi << 8) + cd->bcch_lo; + *ncc = cd->ncc; + *bcc = cd->bcc; +} + +/* decode "Power Command" (10.5.2.28) and (10.5.2.28a) */ +static int gsm48_decode_power_cmd_acc(struct gsm48_power_cmd *pc, uint8_t *power_level uint8_t *atc) +{ + *power_level = pc->power_level; + if (atc) /* only in case of 10.5.2.28a */ + *atc = pc->atc; +} + +/* decode "Synchronization Indication" (10.5.2.39) */ +static int gsm48_decode_power_cmd_acc(struct gsm48_rrlayer *rr, struct gsm48_rr_sync_ind *si) +{ + rr->ho_sync_ind = si->si; + rr->ho_rot = si->rot; + rr->ho_nci = si->nci; +} + +/* receiving HANDOVER COMMAND message (9.1.15) */ +static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - wirklich sizeof(*ho); + struct tlv_parsed tp; + struct gsm48_rr_cd cd; + struct msgb *nmsg; + + memset(&cd, 0, sizeof(cd)); + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of HANDOVER COMMAND message.\n"); + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, ho->data, payload_len, 0, 0); + + /* decode Cell Description */ + gsm_decode_cell_desc(&ho->cell_desc, &cd.bcch_arfcn, &cd.ncc, &cd.bcc); + /* Channel Description */ + memcpy(&rr->chan_desc.chan_desc, ho->chan_desc, 3); + /* Handover Reference */ + rr->hando_ref = ho->ho_ref; + /* Power Command and access type */ + gsm_decode_power_cmd_acc((struct gsm48_power_cmd *)&ho->power_command, + &cd.power_level, cd.atc); + /* Synchronization Indication */ + if (TLVP_PRESENT(&tp, GSM48_IE_SYNC_IND)) + gsm48_decode_sync_ind(rr, + TLVP_VAL(&tp, GSM48_IE_SYNC_IND)-1, &cd); + /* Frequency Sort List */ + if (TLVP_PRESENT(&tp, GSM48_IE_FREQ_SHORT_LIST)) + gsm48_decode_freq_list(&ms->support, s->freq, + TLVP_VAL(&tp, GSM48_IE_FREQ_SHORT_LIST), + *(TLVP_VAL(&tp, GSM48_IE_FREQ_SHORT_LIST)-1), + 0xce, FREQ_TYPE_SERV); + + +today: more IE parsing + + /* store current channel descriptions, to return in case of failure */ + memcpy(&rr->chan_last, &rr->chan_desc, sizeof(*cd)); + /* copy new description */ + memcpy(&rr->chan_desc, cd, sizeof(cd)); + + /* start suspension of current link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gsm48_send_rsl(ms, RSL_MT_SUSP_REQ, msg); + + /* change into special handover suspension state */ + rr->hando_susp_state = 1; + rr->resume_last_state = 0; + + return 0; +} + +static int gsm48_rr_estab_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) +{ + if (rr->hando_susp_state || rr->assign_susp_state) { + if (rr->resume_last_state) { + rr->resume_last_state = 0; + gsm48_rr_tx_ass_cpl(ms, GSM48_RR_CAUSE_NORMAL); + } else { + gsm48_rr_tx_ass_fail(ms, GSM48_RR_CAUSE_PROTO_ERR_UNSPEC); + } + /* transmit queued frames during ho / ass transition */ + gsm48_rr_dequeue_down(ms); + } + + return 0; +} + +static int gsm48_rr_connect_cnf(struct osmocom_ms *ms, struct msgbl *msg) +{ +} + +static int gsm48_rr_mdl_error_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct msgb *nmsg; + uint8_t cause = rllh->data[2]; + + printing of the cause + + switch (cause) { + case RLL_CAUSE_SEQ_ERR: + case RLL_CAUSE_UNSOL_DM_RESP_MF: + einige muessen ignoriert werden + andere gelten als release + } + + if (rr->hando_susp_state || rr->assign_susp_state) { + if (!rr->resume_last_state) { + rr->resume_last_state = 1; + + /* get old channel description */ + memcpy(&rr->chan_desc, &rr->chan_last, sizeof(*cd)); + + /* change radio to old channel */ + tx_ph_dm_est_req(ms, rr->cd_now.arfcn, + rr->cd_now.chan_nr, rr->cd_now.tsc); + + /* re-establish old link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + return gsm48_send_rsl(ms, RSL_MT_RECON_REQ, nmsg); + } + rr->resume_last_state = 0; + } + + /* deactivate channel */ + tx_ph_dm_rel_req(ms, arfcn, rr->chan_desc.chan_desc.chan_nr); + + /* send abort ind to upper layer */ + nmsg = gsm48_mm_msgb_alloc(); + + if (!msg) + return -ENOMEM; + nrrh = (struct gsm_mm_hdr *)nmsg->data; + nrrh->msg_type = RR_ABORT_IND; + nrrh->cause = GSM_MM_CAUSE_LINK_FAILURE; + return gsm48_rr_upmsg(ms, msg); +} + +static void timeout_rr_t3124(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct msgb *nmsg; + + /* stop sending more access bursts when timer expired */ + hando_acc_left = 0; + + /* get old channel description */ + memcpy(&rr->chan_desc, &rr->chan_last, sizeof(*cd)); + + /* change radio to old channel */ + tx_ph_dm_est_req(ms, rr->cd_now.arfcn, rr->cd_now.chan_nr, + rr->cd_now.tsc); + + /* re-establish old link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + return gsm48_send_rsl(ms, RSL_MT_REEST_REQ, nmsg); + + todo +} + +/* send HANDOVER ACCESS burst (9.1.14) */ +static int gsm48_rr_tx_hando_access(struct osmocom_ms *ms) +{ + nmsg = msgb_alloc_headroom(20, 16, "HAND_ACCESS"); + if (!nmsg) + return -ENOMEM; + *msgb_put(nmsg, 1) = rr->hando_ref; + todo burst + return gsm48_send_rsl(ms, RSL_MT_RAND_ACC_REQ, nmsg); +} + +/* send next channel request in dedicated state */ +static int gsm48_rr_rand_acc_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + int s; + + if (!rr->hando_susp_state) { + LOGP(DRR, LOGL_NOTICE, "Random acces confirm, but not in handover state.\n"); + return 0; + } + + /* send up to four handover access bursts */ + if (rr->hando_acc_left) { + rr->hando_acc_left--; + gsm48_rr_tx_hando_access(ms); + return; + } + + /* start timer for sending next HANDOVER ACCESS bursts afterwards */ + if (!bsc_timer_pending(&rr->t3124)) { + if (allocated channel is SDCCH) + start_rr_t3124(rr, GSM_T3124_675); + else + start_rr_t3124(rr, GSM_T3124_320); + if (!rr->n_chan_req) { + start_rr_t3126(rr, 5, 0); /* TODO improve! */ + return 0; + } + rr->n_chan_req--; + + /* wait for PHYSICAL INFORMATION message or T3124 timeout */ + return 0; + +} + +#endif + + |